提交 312b7c79 编写于 作者: K kernelai

refactor: mv wiki doc to repo (#1181)

上级 c2907dfd
......@@ -104,7 +104,7 @@ More details on [Performance](https://github.com/Qihoo360/pika/wiki/3.2.x-Perfor
## Documents
1. [Wiki](https://github.com/Qihoo360/pika/wiki)
1. [Wiki](docs/images/../catalogue.md)
## Contact Us
......
## Performance:
### 0. Env
Disk: 2T NVME
CPU: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz * 40
Memory: 256G
Network Card: 10-Gigabit
OS: CentOS Linux release 7.4
### 1. Single Db
#### 1.1 Write binlog with one slave
data size: 64bytes
key num: 1,000,000
| TEST | QPS |
| ---- | ------ |
| set | 124347 |
| get | 283849 |
#### 1.2 No binlog No slave
![singleDb](https://whoiami.github.io/public/images/images/singleDb.png)
#### 1.3 Benchmark Result
| | WithBinlog&Slave QPS | NoBinlog&Slave QPS |
| ------------------------------- | -------------------- | ------------------ |
| PING_INLINE | 262329 | 272479 |
| PING_BULK | 262467 | 270562 |
| SET | 124953 | 211327 |
| GET | 284900 | 292568 |
| INCR | 120004 | 213766 |
| MSET (10 keys) | 64863 | 111578 |
| MGET (10 keys) | 224416 | 223513 |
| MGET (100 keys) | 29935 | 29550 |
| MGET (200 keys) | 15128 | 14912 |
| LPUSH | 117799 | 205380 |
| RPUSH | 117481 | 205212 |
| LPOP | 112120 | 200320 |
| RPOP | 119932 | 207986 |
| LRANGE_10 (first 10 elements) | 277932 | 284414 |
| LRANGE_100 (first 100 elements) | 165118 | 164355 |
| LRANGE_300 (first 300 elements) | 54907 | 55096 |
| LRANGE_450 (first 450 elements) | 36656 | 36630 |
| LRANGE_600 (first 600 elements) | 27540 | 27510 |
| SADD | 126230 | 208768 |
| SPOP | 103135 | 166555 |
| HSET | 122443 | 214362 |
| HINCRBY | 114757 | 208942 |
| HINCRBYFLOAT | 114377 | 208550 |
| HGET | 284900 | 290951 |
| HMSET (10 fields) | 58937 | 111445 |
| HMGET (10 fields) | 203624 | 205592 |
| HGETALL | 166861 | 160797 |
| ZADD | 106780 | 189178 |
| ZREM | 112866 | 201938 |
| PFADD | 4708 | 4692 |
| PFCOUNT | 27412 | 27345 |
| PFMERGE | 478 | 494 |
#### 1.4 Compare Wiht Redis
With Redis AOF configuration `appendfsync everysec`, redis basically write data into memeory. However, pika uses rocksdb, which writes WAL on ssd druing every write batch. That comparation becomes multiple threads sequential write into ssd and one thread write into memory.
Put the fairness or unfairness aside. Here is the performance.
![](https://whoiami.github.io/public/images/images/CompareWithRedis.png)
### 2. Cluster (Codis)
#### 2.1 Topology:
WithBInlog&Slave
4 machine * 2 pika instance (Master)
4 machine * 2 pika instance (Slave)
NoBinlog&Slave
4 machine * 2 pika instance (Master)
<bt/></bt>
Slots Distribution:
![](https://whoiami.github.io/public/images/images/new_topo.png)
#### 2.2 WithBinlog&Slave
![Set](https://whoiami.github.io/public/images/images/new_withbinlogslave.png)
| Command | QPS |
| ------- | ---------- |
| Set | 1,400,000+ |
#### 2.3 NoBinlog&Slave
![](https://whoiami.github.io/public/images/images/new_nobinlog.png)
| Command | QPS |
| ------- | ---------- |
| Set | 1,600,000+ |
#### 2.4 Get Command
![](https://whoiami.github.io/public/images/images/new_get.png)
| Command | QPS |
| ------- | ---------- |
| Get | 2,300,000+ |
With or without binlog, for Get command, QPS is approximately the same.
\ No newline at end of file
## 概况
- [Pika介绍](introduce.md)
- [FAQ](ops/FAQ.md)
- [社区贡献文档](https://github.com/Qihoo360/pika/wiki/ArticlesFromUsers)
## 使用与运维
- [安装使用](wiki/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8)
- [支持的语言和客户端](wiki/%E6%94%AF%E6%8C%81%E7%9A%84%E8%AF%AD%E8%A8%80%E5%92%8C%E5%AE%A2%E6%88%B7%E7%AB%AF)
- [当前支持的Redis接口以及兼容情况](wiki/pika-%E6%94%AF%E6%8C%81%E7%9A%84redis%E6%8E%A5%E5%8F%A3%E5%8F%8A%E5%85%BC%E5%AE%B9%E6%83%85%E5%86%B5)
- [配置文件说明](wiki/pika-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%B4%E6%98%8E)
- [数据目录说明](wiki/pika-%E6%95%B0%E6%8D%AE%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E)
- [info信息说明](wiki/pika-info%E4%BF%A1%E6%81%AF%E8%AF%B4%E6%98%8E)
- [部分管理指令说明](wiki/pika%E7%9A%84%E4%B8%80%E4%BA%9B%E7%AE%A1%E7%90%86%E5%91%BD%E4%BB%A4%E6%96%B9%E5%BC%8F%E8%AF%B4%E6%98%8E)
- [差异化命令](wiki/pika-%E5%B7%AE%E5%BC%82%E5%8C%96%E5%91%BD%E4%BB%A4)
- [Pika Sharding Tutorials](https://github.com/Qihoo360/pika/wiki/Pika-Sharding-Tutorials)
- [Pika双主](wiki/Pika双主文档)
- [Pika订阅](wiki/Pub-Sub使用)
- [配合sentinel(哨兵)实现pika自动容灾](%E9%85%8D%E5%90%88sentinel(%E5%93%A8%E5%85%B5)%E5%AE%9E%E7%8E%B0pika%E8%87%AA%E5%8A%A8%E5%AE%B9%E7%81%BE)
- [如何升级到Pika3.0](%E5%A6%82%E4%BD%95%E5%8D%87%E7%BA%A7%E5%88%B0Pika3.0)
- [如何升级到Pika3.1或3.2](%e5%a6%82%e4%bd%95%e5%8d%87%e7%ba%a7%e5%88%b0Pika3.1%e6%88%963.2)
- [Pika多库版命令、参数变化参考](https://github.com/Qihoo360/pika/wiki/pika%e5%a4%9a%e5%ba%93%e7%89%88%e5%91%bd%e4%bb%a4%e3%80%81%e5%8f%82%e6%95%b0%e5%8f%98%e5%8c%96%e5%8f%82%e8%80%83)
- [Pika分片版本命令](https://github.com/Qihoo360/pika/wiki/Pika%E5%88%86%E7%89%87%E5%91%BD%E4%BB%A4)
- [副本一致性使用说明](https://github.com/Qihoo360/pika/wiki/%E5%89%AF%E6%9C%AC%E4%B8%80%E8%87%B4%E6%80%A7%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Pika内存使用](https://github.com/Qihoo360/pika/wiki/Pika-Memory-Usage)
- [Pika最佳实践](https://github.com/Qihoo360/pika/wiki/Pika-Best-Practice)
## 设计与实现
- [整体架构](wiki/%E6%95%B4%E4%BD%93%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84)
- [线程模型](wiki/pika-%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B)
- [全同步](wiki/pika-%E5%85%A8%E5%90%8C%E6%AD%A5)
- [增量同步](https://github.com/Qihoo360/pika/wiki/pika-%E5%A2%9E%E9%87%8F%E5%90%8C%E6%AD%A5)
- [副本一致性](https://github.com/Qihoo360/pika/wiki/%E5%89%AF%E6%9C%AC%E4%B8%80%E8%87%B4%E6%80%A7%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3)
- [快照式备份](wiki/pika-%E5%BF%AB%E7%85%A7%E5%BC%8F%E5%A4%87%E4%BB%BD%E6%96%B9%E6%A1%88)
- [锁的应用](https://github.com/Qihoo360/pika/wiki/pika-%E9%94%81%E7%9A%84%E5%BA%94%E7%94%A8)
- [nemo存储引擎数据格式](wiki/pika-nemo%E5%BC%95%E6%93%8E%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E6%A0%BC%E5%BC%8F)
- [blackwidow存储引擎数据格式](wiki/pika-blackwidow%E5%BC%95%E6%93%8E%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E6%A0%BC%E5%BC%8F)
## 性能
- [3.2.x性能](https://github.com/Qihoo360/pika/wiki/3.2.x-Performance)
## 工具包
- [新,旧,可读三类binlog转换工具](wiki/%E6%96%B0%EF%BC%8C%E6%97%A7%EF%BC%8C%E5%8F%AF%E8%AF%BB-%E4%B8%89%E7%B1%BBbinlog%E8%BD%AC%E6%8D%A2%E5%B7%A5%E5%85%B7)
- [根据时间戳恢复数据工具](wiki/%E6%A0%B9%E6%8D%AE%E6%97%B6%E9%97%B4%E6%88%B3%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE%E5%B7%A5%E5%85%B7)
- [Redis到Pika迁移工具](wiki/Redis%E5%88%B0pika%E8%BF%81%E7%A7%BB%E5%B7%A5%E5%85%B7)
- [Redis请求实时copy到Pika工具](wiki/Redis%E8%AF%B7%E6%B1%82%E5%AE%9E%E6%97%B6copy%E5%88%B0pika%E5%B7%A5%E5%85%B7)
- [Pika到Pika、Redis迁移工具](wiki/pika%e5%88%b0pika%e3%80%81redis%e8%bf%81%e7%a7%bb%e5%b7%a5%e5%85%b7)
- [Pika的kv数据写入txt文本工具](wiki/%e8%bf%81%e7%a7%bbString%e7%b1%bb%e5%9e%8b%e6%95%b0%e6%8d%ae%e5%88%b0txt%e6%96%87%e6%9c%ac)
- [kv数据txt文本迁移Pika工具](wiki/txt_to_pika工具)
- [pika exporter监控工具](https://github.com/pourer/pika_exporter)
## Develop
- [Pika coding style](https://github.com/Qihoo360/pika/wiki/cpp---coding-style)
- [Pika 代码梳理](Pika%E4%BB%A3%E7%A0%81%E6%A2%B3%E7%90%86)
- [2022年开发计划](https://github.com/OpenAtomFoundation/pika/issues/1141)
## 博客
- [志哥](http://baotiao.github.io/page2/)pika之父。
- [宋老师](http://kernelmaker.github.io/)的文章里有大量的leveldb/rocksdb分析的文章,对理解rocksdb很有帮助。
- [吴老师](https://axlgrep.github.io/)的文章里包含了leveldb/redis实现的分析。
- [赵明寰](https://whoiami.github.io/)的文章理解pika设计的首选。
### 概述
Pika 一款开源的高性能持久化的NoSQL产品,兼容Redis协议,数据持久化存储到RocksDB存储引擎,具有两种运行模式: 经典模式(Classic) & 分布式模式(Sharding)。
* 经典模式(Classic): 即1主N从同步模式,1个主实例存储所有的数据,N个从实例完全镜像同步主实例的数据,每个实例支持多个DBs。DB默认从0开始,Pika的配置项databases可以设置最大DB数量。DB在Pika上的物理存在形式是一个文件目录。
* 分布式模式(Sharding): Sharding模式下,将用户存储的数据集合称为Table,每个Table切分成多个分片,每个分片称为Slot,对于某一个KEY的数据由哈希算法计算决定属于哪个Slot。将所有Slots及其副本按照一定策略分散到所有的Pika实例中,每个Pika实例有一部分主Slot和一部分从Slot。在Sharding模式下,分主从的是Slot而不再是Pika实例。Slot在Pika上的物理存在形式是一个文件目录。
Pika可以通过配置文件中的instance-mode配置项,设置为classic和sharding,来选择运行经典模式(Classic)还是分布式模式(Sharding)的Pika。
### 1. 经典(主从)模式
![](https://raw.githubusercontent.com/simpcl/simpcl.github.io/master/PikaClassic.png)
### 2. 分布式模式
![](https://raw.githubusercontent.com/simpcl/simpcl.github.io/master/PikaCluster.png)
\ No newline at end of file
Blackwidow本质上是基于rocksdb的封装,使本身只支持kv存储的rocksdb能够支持多种数据结构, 目前Blackwidow支持五种数据结构的存储:String结构(实际上就是存储key, value), Hash结构,List结构,Set结构和ZSet结构, 因为Rocksdb的存储方式只有kv一种, 所以上述五种数据结构最终都要落盘到Rocksdb的kv存储方式上,下面我们展示Blackwidow和rocksdb的关系并且说明我们是如何用kv来模拟多数据结构的。
![](https://i.imgur.com/nqeliuv.png)
## 1. String结构的存储
String本质上就是Key, Value, 我们知道Rocksdb本身就是支持kv存储的, 我们为了实现Redis中的expire功能,所以在value后面添加了4 Bytes用于存储timestamp, 作为最后Rocksdb落盘的kv格式,下面是具体的实现方式:
![](https://i.imgur.com/KnA707a.png)
如果我们没有对该String对象设置超时时间,则timestamp存储的值就是默认值0, 否则就是该对象过期时间的时间戳, 每次我们获取一个String对象的时候, 首先会解析Value部分的后四字节, 获取到timestamp做出判断之后再返回结果。
## 2. Hash结构的存储
blackwidow中的hash表由两部分构成,元数据(meta_key, meta_value), 和普通数据(data_key, data_value), 元数据中存储的主要是hash表的一些信息, 比如说当前hash表的域的数量以及当前hash表的版本号和过期时间(用做秒删功能), 而普通数据主要就是指的同一个hash表中一一对应的field和value,作为具体最后Rocksdb落盘的kv格式,下面是具体的实现方式:
1. 每个hash表的meta_key和meta_value的落盘方式:
![](https://i.imgur.com/YLP48rg.png)
meta_key实际上就是hash表的key, 而meta_value由三个部分构成: 4Bytes的Hash size(用于存储当前hash表的大小) + 4Bytes的Version(用于秒删功能) + 4Bytes的Timestamp(用于记录我们给这个Hash表设置的超时时间的时间戳, 默认为0)
2. hash表中data_key和data_value的落盘方式:
![](https://i.imgur.com/phiBsqd.png)
data_key由四个部分构成: 4Bytes的Key size(用于记录后面追加的key的长度,便与解析) + key的内容 + 4Bytes的Version + Field的内容, 而data_value就是hash表某个field对应的value。
3. 如果我们需要查找一个hash表中的某一个field对应的value, 我们首先会获取到meta_value解析出其中的timestamp判断这个hash表是否过期, 如果没有过期, 我们可以拿到其中的version, 然后我们使用key, version,和field拼出data_key, 进而找到对应的data_value(如果存在的话)
## 3. List结构的存储
blackwidow中的list由两部分构成,元数据(meta_key, meta_value), 和普通数据(data_key, data_value), 元数据中存储的主要是list链表的一些信息, 比如说当前list链表结点的的数量以及当前list链表的版本号和过期时间(用做秒删功能), 还有当前list链表的左右边界(由于nemo实现的链表结构被吐槽lrange效率低下,所以这次blackwidow我们底层用数组来模拟链表,这样lrange速度会大大提升,因为结点存储都是有序的), 普通数据实际上就是指的list中每一个结点中的数据,作为具体最后Rocksdb落盘的kv格式,下面是具体的实现方式
1. 每个list链表的meta_key和meta_value的落盘方式:
![](https://i.imgur.com/083SjIc.png)
meta_key实际上就是list链表的key, 而meta_value由五个部分构成: 8Bytes的List size(用于存储当前链表中总共有多少个结点) + 4Bytes的Version(用于秒删功能) + 4Bytes的Timestamp(用于记录我们给这个List链表设置的超时时间的时间戳, 默认为0) + 8Bytes的Left Index(数组的左边界) + 8Bytes的Right Index(数组的右边界)
2. list链表中data_key和data_value的落盘方式:
![](https://i.imgur.com/FBBn6kd.png)
data_key由四个部分构成: 4Bytes的Key size(用于记录后面追加的key的长度,便与解析) + key的内容 + 4Bytes的Version + 8Bytes的Index(这个记录的就是当前结点的在这个list链表中的索引), 而data_value就是list链表该node中存储的值
## 4. Set结构的存储
blackwidow中的set由两部分构成,元数据(meta_key, meta_value), 和普通数据(data_key, data_value), 元数据中存储的主要是set集合的一些信息, 比如说当前set集合member的数量以及当前set集合的版本号和过期时间(用做秒删功能), 普通数据实际上就是指的set集合中的member,作为具体最后Rocksdb落盘的kv格式,下面是具体的实现方式:
1. 每个set集合的meta_key和meta_value的落盘方式:
![](https://i.imgur.com/bQeVvSj.png)
meta_key实际上就是set集合的key, 而meta_value由三个部分构成: 4Bytes的Set size(用于存储当前Set集合的大小) + 4Bytes的Version(用于秒删功能) + 4Bytes的Timestamp(用于记录我们给这个set集合设置的超时时间的时间戳, 默认为0)
2. set集合中data_key和data_value的落盘方式:
![](https://i.imgur.com/d2ctqPo.png)
data_key由四个部分构成: 4Bytes的Key size(用于记录后面追加的key的长度,便与解析) + key的内容 + 4Bytes的Version + member的内容, 由于set集合只需要存储member, 所以data_value实际上就是空串
## 5. ZSet结构的存储
blackwidow中的zset由两部部分构成,元数据(meta_key, meta_value), 和普通数据(data_key, data_value), 元数据中存储的主要是zset集合的一些信息, 比如说当前zset集合member的数量以及当前zset集合的版本号和过期时间(用做秒删功能), 而普通数据就是指的zset中每个member以及对应的score, 由于zset这种数据结构比较特殊,需要按照memer进行排序,也需要按照score进行排序, 所以我们对于每一个zset我们会按照不同的格式存储两份普通数据, 在这里我们称为member to score和score to member,作为具体最后Rocksdb落盘的kv格式,下面是具体的实现方式:
1. 每个zset集合的meta_key和meta_value的落盘方式:
![](https://i.imgur.com/RhZ8KMw.png)
meta_key实际上就是zset集合的key, 而meta_value由三个部分构成: 4Bytes的ZSet size(用于存储当前zSet集合的大小) + 4Bytes的Version(用于秒删功能) + 4Bytes的Timestamp(用于记录我们给这个Zset集合设置的超时时间的时间戳, 默认为0)
2. 每个zset集合的data_key和data_value的落盘方式(member to score):
![](https://i.imgur.com/C85Ba5Z.png)
member to socre的data_key由四个部分构成:4Bytes的Key size(用于记录后面追加的key的长度,便与解析) + key的内容 + 4Bytes的Version + member的内容, data_value中存储的其member对应的score的值,大小为8个字节,由于rocksdb默认是按照字典序进行排列的,所以同一个zset中不同的member就是按照member的字典序来排列的(同一个zset的key size, key, 以及version,也就是前缀都是一致的,不同的只有末端的member).
3. 每个zset集合的data_key和data_value的落盘方式(score to member):
![](https://i.imgur.com/QV9XHEk.png)
score to member的data_key由五个部分构成:4Bytes的Key size(用于记录后面追加的key的长度,便与解析) + key的内容 + 4Bytes的Version + 8Bytes的Score + member的内容, 由于score和member都已经放在data_key中进行存储了所以data_value就是一个空串,无需存储其他内容了,对于score to member中的data_key我们自己实现了rocksdb的comparator,同一个zset中score to member的data_key会首先按照score来排序, 在score相同的情况下再按照member来排序
## Blackwidow相对于Nemo有哪些优势
1. Blackwidow采用了rocksdb的column families的新特性,将元数据和实际数据分开存放(对应于上面的meta数据和data数据), 这种存储方式相对于Nemo将meta, data混在一起存放更加合理, 并且可以提升查找效率(比如info keyspace的效率会大大提升)
2. Blackwidow中参数传递大量采用Slice而Nemo中采用的是std::string, 所以Nemo会有很多没有必要的string对象的构造函数以及析构函数的调用,造成额外的资源消耗,而Blackwidow则不会有这个问题
3. Blackwidow对kv模拟多数据结构的存储格式上做了重新设计(具体可以参考Nemo引擎数据存储格式和本篇文章),使之前在Nemo上出现的一些无法解决的性能问题得以解决,所以Blackwidow的多数据结构在某些场景下性能远远优于Nemo
4. 原来Nemo对多数据结构的Key的长度最大只能支持到256 Bytes,而Blackwidow经过重新设计,放开了多数据结构Key长度的这个限制
5. Blackwidow相对于Nemo更加节省空间,Nemo由于需要nemo-rocksdb的支持,所以不管在meta还是data数据部分都追加了version和timestamp这些信息,并且为了区分meta_key和data_key, 在最前面加入s和S(拿Set数据结构打比方),Blackwidow在这方面做了优化,使同样的数据量下Blackwidow所占用的空间比Nemo要小(举个例子,Blackwidow中List结构中的一个Node就比Nemo中的一个Node节省了16 Bytes的空间)
6. Blackwidow在锁的实现上参照了RocksDB事务里锁的实现方法,而弃用了之前Nemo的行锁,所以在多线程对同一把锁有抢占的情况下性能会有所提升
### 目前线程模型
非一致性场景
1,客户端请求加锁写入db和binlog文件
2,将结果返回客户端
3,通过发送BinlogSync请求向从库同步
4,从库返回BinlogSyncAck报告同步状况
![](https://s1.ax1x.com/2020/03/27/GPVfBj.png)
一致性场景
1,客户端请求先写入binlog文件
2,通过发送BinlogSync请求向从库同步
3,从库返回BinlogSyncAck报告同步状况
4,将相应的请求写入db
5,将结果返回客户端
![](https://s1.ax1x.com/2020/03/27/GPVINq.png)
<br>
### Binlog Header变动
```
/*
* *****************Type First Binlog Item Format******************
* |<Type>|<CreateTime>|<Term Id>|<Logic Id>|<File Num>|<Offset>|<Content Length>|<Content>|
* | 2 | 4 | 4 | 8 | 4 | 8 | 4 | ... |
* |----------------------------------------- 34 Bytes ------------------------------------|
*/
```
其中 TermId, 和Logic id是沿用[Raft](https://raft.github.io/raft.pdf)论文中term 和log index 的概念。具体的详见论文。
其中 File Num 和offset 是本条binlog在文件中的偏移量。
Pika的Binlog存在的意义是为了保证主从能够增量同步,而Raft Log存在的意义是为了保证Leader 和Follower 的数据够一致。某种意义上说这两个"Log"的概念是一样的,所以在实现上,将binlog 和 Raft Log 复用成一条log,目前的binlog header 中 Term Id 和 Logic Id 属于 Raft Log(简称日志)的信息,而File Num 和Offset 属于Binlog 的信息。
<br>
### 一致性协议的三阶段
日志恢复和复制基本按照Raft论文当中所做操作,这里不做过多解释。实现上,这里分为三个阶段。分别是日志的复制,日志的恢复,日志的协商。
熟悉Raft协议的读者可能会发现,这里的三个阶段跟Raft日志复制不是完全一样。在Pika的实现当中,基于pika现有的代码结构,我们把Leader 和Follower同步点位回退的逻辑单独提取出来,形成了Pika Trsysync的状态。任何日志的复制出错,pika会终止当前的日志复制(BinlogSync)状态,转而将状态机转化成Trysync的状态,进而Leader 和Follower 会进入日志的协商逻辑。协商成功之后会转入日志复制的逻辑。
![](https://s1.ax1x.com/2020/03/27/GPVl7R.png)
<br>
### 日志复制
日志的逻辑结构如下,上面部分是Leader可能的log点位,下部分是Follower可能的log点位。
![](https://s1.ax1x.com/2020/03/27/GPVH3T.png)
1,日志的复制的逻辑可以参考Raft协议的逻辑,这里举例说说明客户端从请求到返回,日志经过了怎样的流程。
Leader Status:
Committed Index : 10
Applied Index:8
Last Index: 15
<br>
Follower Status:
Committed Index : 7
Applied Index:5
LastIndex: 12
2,当Leader 发送13-15的日志到Follower的时候,Follower的状态会做如下更新:
Follower Status:
Committed Index : 10
Applied Index:5
LastIndex: 15
这时候日志6-10都是可以被应用到状态机的。但是对于日志11-15来说只能等到下一次收到Leader Committed Index 大于15的时候这些日志才能够被更新,这时候如果客户端没有继续写入,Follower的Committed index可以依靠ping消息(携带了Leader 的committed index)进行更新。
3,当Leader 接收到Follower的ack信息的时候,Leader 状态会做如下更新:
Leader Status:
Committed Index : 15
Applied Index: 8
Last Index: 15
此时日志9-15都是可以被应用到状态机,这里是写db,当日志9写入db之后,就会返回客户端,相应的Applied Index 更新为9。这时候日志9就可以返回客户端。
对于从来说,整体的日志复制的逻辑还是按照Raft论文当中进行的。唯一不同的是论文中日志回退的一部分逻辑放到了日志协商阶段进行。
<br>
### 日志恢复:
重启pika的时候,根据持久话的一致性信息(applied index 等)回复出之前的一致性状态。
<br>
### 日志协商:
这个阶段Follower节点主动发起Trysync流程,携带Last Index,向Leader发送协商同步点位请求。协商过程如下:
Follower携带last_index发动到Leader, Leader 通过Follower的last_index位置判断是否自己能够找到last_index对应的自己的相应的log,如果找到log并且两个log一致,Leader返回okay协商结束。如果没有找到,或者log不一致,Leader向Follower发送hints,hints是Leader本地的最新的日志。Follower通过hints,回退本地日志,更新自己的last_index,重新向主协商。最终Leader Follower达成一致,结束TrySync流程,进行日志复制流程。
![](https://s1.ax1x.com/2020/03/27/GPVbgU.png)
Leader 日志协商的伪代码如下:
```c++
Status LeaderNegotiate() {
reject = true
if (follower.last_index > last_index) {
send[last_index - 100, last_index]
} else if (follower.last_index < first_index) {
need dbsync
}
if (follower.last_index not found) {
need dbsync
}
if (follower.last_index found but term not equal) {
send[found_index - 100, found_index]
}
reject = false
return ok;
}
```
Follower日志协商的伪代码日下:
```c++
Status FollowerNegotiate() {
if last_index > hints[hints.size() - 1] {
TruncateTo(hints[hints.size() - 1]);
}
for (reverse loop hints) {
if (hint.index exist && hint.term == term) {
TruncateTo(hint.index)
send trysync with log_index = hint.index
return ok;
}
}
// cant find any match
TruncateTo(hints[0])
send trysync with log_index = last_index
}
```
以上介绍了关于日志的具体三个阶段。整体的逻辑遵从与Raft论文的设计,在实现细节上根据Pika目前的代码结构进行了一些列的调整。
### 关于选主和成员变换
目前选主需要管理员手动介入,详见[副本一致性使用文档](https://github.com/Qihoo360/pika/wiki/%E5%89%AF%E6%9C%AC%E4%B8%80%E8%87%B4%E6%80%A7%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
成员变换的功能目前暂不支持。
\ No newline at end of file
pika作为类redis的存储系统,为了弥补在性能上的不足,在整个系统中大量使用[多线程的结构](https://github.com/Qihoo360/pika/wiki/pika-%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B),涉及到多线程编程,势必需要为线程加锁来保证数据访问的一致性和有效性。其中主要用到了三种锁
1. 互斥锁
1. 读写锁
2. 行锁
## 读写锁
### 应用场景
应用挂起指令,在挂起指令的执行中,会添加写锁,以确保,此时没有其他指令执行。其他的普通指令在会添加读锁,可以并行访问。
其中挂起指令有:
1. trysync
2. bgsave
3. flushall
4. readonly
#### 作用和意义
保证当前服务器在执行挂起指令时,起到阻写作用。
## 行锁
行锁,用于对一个key加锁,保证同一时间只有一个线程对一个key进行操作。
### 作用和意义:
pika中存取的数据都是类key,value数据,不同key所对应的数据完全独立,所以只需要对key加锁可以保证数据在并发访问时的一致性,行锁相对来说,锁定粒度小,也可以保证数据访问的高效性。
### 应用场景
在pika系统中,对于数据库的操作都需要添加行锁,主要在应用于两个地方,在系统上层指令过程中和在数据引擎层面。在pika系统中,对于写指令(会改变数据状态,如SET,HSET)需要除了更新数据库状态,还涉及到pika的[增量同步](https://github.com/Qihoo360/pika/wiki/pika-%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5%E5%8A%9F%E8%83%BD),需要在binlog中添加所执行的写指令,用于保证master和slave的数据库状态一致。故一条写指令的执行,主要有两个部分:
1. 更改数据库状态
2. 将指令添加到binlog中
其加锁情况,如下图:
![](http://ww4.sinaimg.cn/large/c2cd4307jw1f6no7d5557j20fa0ma74x.jpg)
#### 设计的平衡
在图中可以看到,对同一个key,加了两次行锁,在实际应用中,pika上所加的锁就已经能够保证数据访问的正确性。如果只是为了pika所需要的业务,blackwidow层面使用行锁是多余的,但是[blackwidow的设计](https://github.com/Qihoo360/pika/wiki/pika-blackwidow引擎数据存储格式)初衷就是通过对rocksdb的改造和封装提供一套完整的类redis数据访问的解决方案,而不仅仅是为pika提供数据库引擎。这种设计思路也是秉承了Unix中的设计原则:Write programs that do one thing and do it well。
这样设计大大降低了pika与blackwidow之间的耦合,也使得blackwidow可以被单独拿出来测试和使用,在pika中的[数据迁移工具](https://github.com/Qihoo360/pika/wiki/pika%E5%88%B0redis%E8%BF%81%E7%A7%BB%E5%B7%A5%E5%85%B7)就是完全使用blackwidow来完成,不必依赖任何pika相关的东西。另外对于blackwidow感兴趣或者有需求的团队也可以直接将blackwidow作为数据库引擎而不需要修改任何代码就能使用完整的数据访问功能。
### 具体实现
在pika系统中,一把行锁就可以维护所有key。在行锁的实现上是将一个key与一把互斥锁相绑定,并将其放入哈希表中维护,来保证每次访问key的线程只有一个,但是不可能也不需要为每一个key保留一把互斥锁,只需要当有多条线程访问同一个key时才需要锁,在所有线程都访问结束之后,就可以销毁这个绑定key的互斥锁,释放资源。具体实现如下:
``` C++
class RecordLock {
public:
RecordLock(port::RecordMutex *mu, const std::string &key)
: mu_(mu), key_(key) {
mu_->Lock(key_);
}
~RecordLock() { mu_->Unlock(key_); }
private:
port::RecordMutex *const mu_;
std::string key_;
// No copying allowed
RecordLock(const RecordLock&);
void operator=(const RecordLock&);
};
void RecordMutex::Lock(const std::string &key) {
mutex_.Lock();
std::unordered_map<std::string, RefMutex *>::const_iterator it = records_.find(key);
if (it != records_.end()) {
//log_info ("tid=(%u) >Lock key=(%s) exist, map_size=%u", pthread_self(), key.c_str(), records_.size());
RefMutex *ref_mutex = it->second;
ref_mutex->Ref();
mutex_.Unlock();
ref_mutex->Lock();
//log_info ("tid=(%u) <Lock key=(%s) exist", pthread_self(), key.c_str());
} else {
//log_info ("tid=(%u) >Lock key=(%s) new, map_size=%u ++", pthread_self(), key.c_str(), records_.size());
RefMutex *ref_mutex = new RefMutex();
records_.insert(std::make_pair(key, ref_mutex));
ref_mutex->Ref();
mutex_.Unlock();
ref_mutex->Lock();
//log_info ("tid=(%u) <Lock key=(%s) new", pthread_self(), key.c_str());
}
}
```
完整代码可参考:[slash_mutex.cc](https://github.com/baotiao/slash/blob/master/src/slash_mutex.cc) [slash_mutex.h](https://github.com/baotiao/slash/blob/master/include/slash_mutex.h)
nemo本质上是对rocksdb的改造和封装,使其支持多数据结构的存储(rocksdb只支持kv存储)。总的来说,nemo支持五种数据结构类型的存储:KV键值对(为了区分,nemo的的键值对结构用大写的“KV”表示)、Hash结构、List结构、Set结构和ZSet结构。因为rocksdb的存储方式只有kv一种结构,所以以上所说的5种数据结构的存储最终都要落盘到rocksdb的kv存储方式上。
## 1. KV键值对的存储。
KV存储没有添加额外的元信息,只是在value的结尾加上8个字节的附加信息(前4个字节表示version,后 4个字节表示ttl)作为最后落盘kv的值部分。具体如下图:
![](http://ww2.sinaimg.cn/large/c2cd4307jw1f6nbojnvjjj20k204tq30.jpg)
version字段用于对该键值对进行标记,以便后续的处理,如删除一个键值对时,可以在该version进行标记,后续再进行真正的删除,这样可以减少删除操作所导致的服务阻塞时间。
## 2. Hash结构的存储
对于每一个Hash存储,它包括hash键(key),hash键下的域名(field)和存储的值 (value)。nemo的存储方式是将key和field组合成为一个新的key,将这个新生成的key与所要存储的value组成最终落盘的kv键值对。同时,对于每一个hash键,nemo还为它添加了一个存储元信息的落盘kv,它保存的是对应hash键下的所有域值对的个数。下面的是具体的实现方式:
1. 每个hash键、field、value到落盘kv的映射转换
![image](http://ww3.sinaimg.cn/large/c2cd4307gw1f6m742p5h5j20rg06iaad.jpg)
2. 每个hash键的元信息的落盘kv的存储格式
![](http://ww2.sinaimg.cn/large/c2cd4307jw1f6nbsn0up3j20jx04yweq.jpg)
1. 中前面的横条对应落盘kv的键部分,从前到后,第一个字段是一个字符’h’,表示的是hash结构的key;第二个字段是hash键的字符串长度,用一个字节(uint8_t类型)来表示;第三个字段是hash键的内容,因为第二个字段是一个字节,所以这里限定hash键的最大字符串长度是254个字节;第四个字段是field的内容。a中后面的横条代表的是落盘kv键值对的值部分,和KV结构存储一样,它是存入的value值加上8个字节的version字段和8个字节的ttl字段得到的。
b中前面的横条代表的存储每个hash键的落盘kv键值对的键部分,它有两字段组成,第一个字段是一个’H’字符,表示这存储时hash键的元信息,第二个字段是对应的hash键的字符串内容;b中后面的横条代表的该元信息的值,它表示对应的hash键中的域值对(field-value)的数量,大小为8个字节(类型是int64_t)。
## 3. List结构的存储
顾名思义,每个List结构的底层存储也是采用链表结构来完成的。对于每个List键,它的每个元素都落盘为一个kv键值对,作为一个链表的一个节点,称为元素节点。和hash一样,每个List键也拥有自己的元信息。
a. 每个元素节点对应的落盘kv存储格式
![image](http://ww4.sinaimg.cn/large/c2cd4307gw1f6m74322gaj20qx06mweu.jpg)
b.每个元信息的落盘kv的存储格式
![image](http://ww2.sinaimg.cn/large/c2cd4307gw1f6m7422myxj20m806mdg6.jpg)
a中前面横条代表的是最终落盘kv结构的键部分,总共4个字段,前面三个字符段分别为一个字符’l’(表明是List结构的结存),List键的字符串长度(1个字节)、List键的字符串内容(最多254个字节),第四个字段是该元素节点所对应的索引值,用8个字节表示(int64_t类型),对于每个元素节点,这个索引(sequence)都是唯一的,是其他元素节点访问该元素节点的唯一媒介;往一个空的List键内添加一个元素节点时,该添加的元素节点的sequence为1,下次一次添加的元素节点的sequence为2,依次顺序递增,即使中间有元素被删除了,被删除的元素的sequence也不会被之后新插入的元素节点使用,这就保证了每个元素节点的sequence都是唯一的。b中后面的横条代表的是具体落盘kv结构的值,它有5个字段,后面的三个字段分别为存入的value值、version、ttl,这和前面的hash结构存储是类似的;前两个字段分别表示的是前一个元素节点的sequence、和后一个元素节点的sequence、通过这两个sequence,就可以知道前一个元素节点和后一个元素节点的罗盘kv的键内容,从而实现了一个双向链表的结构。
b中的前面横条表示存储元信息的落盘kv的键部分,和前面的hash结构是类似的;后面的横条表示存储List键的元信息,它有四个字段,从前到后分别为该List键内的元素个数、最左边的元素 节点的sequence(相当于链表头)、最右边的元素节点的sequence(相当于链表尾)、下一个要插入元素节点所应该使用的sequence。
## 4. Set结构的存储
a.每个元素节点对应的落盘kv存储格式
![image](http://ww4.sinaimg.cn/large/c2cd4307gw1f6m741krdxj20kw06mjrk.jpg)
b.每个Set键的元信息对应的落盘kv存储格式
![image](http://ww3.sinaimg.cn/large/c2cd4307gw1f6m742hinvj20lw06o3yo.jpg)
Set结构的存储和hash结构基本是相同的,只是Set中每个元素对应的落盘kv中,值的部分只有version和ttl,没有value字段。
## 5. ZSet结构的存储
ZSet存储结构是一个有序Set,所以对于每个元素,增加了一个落盘kv,在这个增加的落盘kv的键部分,把该元素对应的score值整合进去,这样便于依据score值进行排序(因为从rocksdb内拿出的数据时按键排序的),下面是落盘kv的存储形式。
a. score值在value部分的落盘kv存储格式
![](http://ww1.sinaimg.cn/large/c2cd4307jw1f6nbzzqa64j20oa068jrk.jpg)
b. score值在key部分的落盘kv存储格式
![](http://ww4.sinaimg.cn/large/c2cd4307jw1f6nc0td9l7j20oa068jrk.jpg)
c.存储元信息的落盘kv的存储格式
![](http://ww2.sinaimg.cn/large/c2cd4307jw1f6nc19p731j20ii068t8u.jpg)
a、c与前面的几种数据结构类似,不再赘述。b中的score是从double类型转变过来的int64_t类型,这样做是为了可以让原来的浮点型的score直接参与到字符串的排序当中(浮点型的存储格式与字符串的比较方式不兼容)。
\ No newline at end of file
# Pika 快照式备份方案
## 原理
不同于Redis,Pika的数据主要存储在磁盘中,这就使得其在做数据备份时有天然的优势,可以直接通过文件拷贝实现
实现
![](http://ww4.sinaimg.cn/large/c2cd4307gw1f6m745csxsj20fl0iojss.jpg)
## 流程
- 打快照:阻写(阻止客户端进行写db操作),并在这个过程中获取快照内容
- 异步线程拷贝文件:通过修改Rocksdb提供的BackupEngine拷贝快照中文件,这个过程中会阻止文件的删除
## 快照内容
- 当前db的所有文件名
- manifest文件大小
- sequence_number
- 同步点
- binlog filenum
- offset
\ No newline at end of file
# 全量同步
## 背景
### 1.Pika Replicate
- pika支持master/slave的复制方式,通过slave端的slaveof命令激发
- salve端处理slaveof命令,将当前状态变为slave,改变连接状态
- slave的向master发送MetaSync请求,在同步之前确保自身db的拓扑结构和master一致
- slave下的每个partition单独的向master端对应的partition发起trysync请求,建立同步关系
### 2.Binlog
- pika同步依赖binlog
- binlog文件会自动或手动删除
- 当同步点对应的binlog文件不存在时,需要通过全同步进行数据同步
## 全同步
### 1. 简介
- 需要进行全同步时,master会将db文件dump后发送给slave
- 通过rsync的deamon模式实现db文件的传输
- 默认使用pika port+1000作为rysnc传输端口
### 2. 实现逻辑
1. 在pika实例启动的同时会启动Rsync服务
2. master发现某一个partition需要全同步时,判断是否有备份文件可用,如果没有先dump一份
3. master通过rsync向slave发送对应partition的dump的文件
4. slave的对应partition用收到的文件替换自己的db
5. slave的对应partition用最新的偏移量再次发起trysnc
6. 完成同步
Slave中某一个Partition建立同步:
![slave的partition](https://i.imgur.com/flnOyeZ.png)
Master处理同步请求:
![master执行过程](https://i.imgur.com/Beclo9c.png)
### 3. Slave连接状态
- No Connect:不尝试成为任何其他节点的slave
- ShouldMetaSync:向master请求db的拓扑信息,确保与自身一致
- TryConnect:为每个partition重置状态机,让其处于准备同步的状态
- Connecting:在所有partition没有建立同步关系之前一直是处于connecting的状态
- EstablishSucces: 所有partition建立同步关系成功
- Error:出现了异常
# 增量同步
## 背景 :
<br/></br>
从库Pika得到主库的全部DB结构,接下来以partition维度做Trysync,如果从库确认可以增量同步,从库将以partition为维度进行增量同步。默认使用pika port+2000的端口进行增量同步。
### Binlog 结构:
<br/></br>
Pika的主从同步是使用Binlog来完成的,一主多从的结构master节点也可以给多个slave复用一个Binlog,只不过不同的slave在binglog中有自己的偏移量而已,master执行完一条写命令就将命令追加到Binlog中,pika的同步模块会读出对应的binlog发送到slave,而slave收到binlog后会执行并追加到自己的Binlog中,由于主从偏移量一样,所以一旦发生网络或节点故障需要重连主从时,slave仅需要将自己当前的Binlog偏移量发送给master,master找到后从该偏移量开始同步后续命令,理论上将命令不做处理一条一条追加到文件中,但是这样的记录格式容错很差,如果读文件中写错一个字节则导致整个文件不可用,所以pika采用了类似leveldb log的格式来进行存储,具体如下:
![image](http://ww2.sinaimg.cn/large/c2cd4307gw1f6m74717b3j20rm0gjwgw.jpg)
### 交互过程:
<br/></br>
1,从库发送BinlogSyncRequest 报文,报文中需说明自己已经收到的BinlogOffset。
2,主库收到BinlogSyncRequest之后会从同步点开始发出一批BinlogSyncResponse。
3,从库在收到BinlogSyncResponse之后,会在写入本地binlog之后再进行1流程。
![image](https://i.imgur.com/JVfTV22.png)
## 同步模块:
<br/></br>
![image](https://i.imgur.com/5ByKpsA.png )
Pika的同步由ReplicaManager(RM)模块负责。RM中有两层结构,逻辑层负责同步逻辑,传输层负责链接的管理数据的解析和传输。
数据的同步的最小单位是Partition,每一个Pika实例会维护自己作为主的partition(MasterPartition)和自己是从的partition(SlavePartition)。对于MasterPartition,需要记录跟随自己的slave同步信息,逻辑层会根据该信息向slave同步信息。对于SlavePartition,则是需要需要记录master的信息,逻辑层会按照该信息按需向master发送同步请求。
逻辑层维护两个数据结构,一个是MasterPartitions,记录跟随自己的SlaveNode信息,主要包括slave的同步状态和当前的sessionId。另一个是SlavePartitions,记录主的信息。
传输层分为两个子模块,ReplicationClient负责发起链接的建立,ReplicationServer负责响应报文。每两个实例之间的所有partition复用一条链接。
## 同步过程:
<br/></br>
![image](https://i.imgur.com/1Q8PbjF.png )
### MasterPartition 同步事件
逻辑层处理MasterPartition的同步事件,向其对应的从同步数据。
1,读取MasterPartition Binlog信息后,将BinlogOffsetInfo记录到SlaveNode自己的window中。
2,将Binlog暂存到临时的待发送队列中。
3,辅助线程(Auxiliary thread) 定时将临时待发送队列的数据通过RM的传输层发送给对应的slave节点。
4,收到slave的BinlogSyncResponse之后,得知slave收到的BinlogOffset信息,更新SlaveNode window,重复1流程继续同步
为了控制每个SlaveNode同步的速度,避免某几个SlaveNode占用过多资源,为每一个SlaveNode设置了window。如下图所示,Pika收到了BinlogOffset为100到200的ack response,从window中移除BinlogOffset位于100到200的元素,之后继续发送BinlogOffset为1100和1200的binlog,对应的BinlogOffset添加至window中。
![image](https://i.imgur.com/0GtOhk4.png)
### SlavePartition 同步事件
逻辑层处理SlavePartition的同步事件,收到master发送的同步数据,向master发相应的response信息。
1,按照解析出的Partition信息,被分配到对应的线程处理binlog写入任务。
2,线程写入binlog之后,调用传输层发送BinlogSyncResponse信息。
3,根据binlog的key分配给对应的线程处理写入db任务。
pika使用的是多线程模型,使用多个工作线程来进行读写操作,由底层blackwidow引擎来保证线程安全,线程分为12种:
- PikaServer:主线程
- DispatchThread:监听1个端口,接收用户连接请求
- WorkerThread:存在多个(用户配置),每个线程里有若干个用户客户端的连接,负责接收用户命令,然后将命令封装成一个Task扔到ThreadPool执行,任务执行完毕之后由该线程将reply返回给用户
- ThreadPool:线程池中的线程数量由用户配置,执行WorkerThread调度过来的Task, Task的内容主要是写DB和写Binlog
- PikaAuxiliaryThread:辅助线程,处理同步过程中状态机状态的切换,主从之间心跳的发送以及超时检查
- PikaReplClient:本质上是一个Epoll线程(与其他Pika实例的PikaReplServer进行通信)加上一个由若干线程组成的线程数组(异步的处理写Binlog以及写DB的任务)
- PikaReplServer:本质上是一个Epoll线程(与其他Pika实例的PikaReplClient进行通信)加上一个由若干线程组成的线程池(处理同步的请求以及根据从库返回的Ack更新Binlog滑窗)
- MonitorThread:执行了Monitor命令的客户端会被分配在这个线程上,这个线程将目前Pika正在处理的命令返回给挂在这个线程上的客户端
- KeyScanThread:在这个线程中执行info keyspace 1触发的统计Key数量的任务
- BgSaveThread:对指定的DB进行Dump操作,以及全同步的时候发送Dump数据到从库(对一个DB执行全同步是先后向Thread中扔了BgSave以及DBSync两个任务从而保证顺序)
- PurgeThread:用于清理过期的Binlog文件
- PubSubThread:用于支持PubSub相关功能
## Pika是什么
Pika是DBA和基础架构组联合开发的类Redis 存储系统,所以完全支持Redis协议,用户不需要修改任何代码,就可以将服务迁移至Pika。Pika是一个可持久化的大容量Redis存储服务,兼容string、hash、list、zset、set的绝大接口[兼容详情](https://github.com/Qihoo360/pika/wiki/pika-%E6%94%AF%E6%8C%81%E7%9A%84redis%E6%8E%A5%E5%8F%A3%E5%8F%8A%E5%85%BC%E5%AE%B9%E6%83%85%E5%86%B5),解决Redis由于存储数据量巨大而导致内存不够用的容量瓶颈,并且可以像Redis一样,通过slaveof命令进行主从备份,支持全同步和部分同步。同时DBA团队还提供了迁移工具, 所以用户不会感知这个迁移的过程,迁移是平滑的。
## 与Redis的比较
Pika相对于Redis,最大的不同就是Pika是持久化存储,数据存在磁盘上,而Redis是内存存储,由此不同也给Pika带来了相对于Redis的优势和劣势
### 优势:
1. 容量大:Pika没有Redis的内存限制, 最大使用空间等于磁盘空间的大小
2. 加载db速度快:Pika在写入的时候, 数据是落盘的, 所以即使节点挂了, 不需要rdb或者oplog,Pika重启不用加载所有数据到内存就能恢复之前的数据, 不需要进行回放数据操作。
3. 备份速度快:Pika备份的速度大致等同于cp的速度(拷贝数据文件后还有一个快照的恢复过程,会花费一些时间),这样在对于百G大库的备份是快捷的,更快的备份速度更好的解决了主从的全同步问题
### 劣势:
由于Pika是基于内存和文件来存放数据, 所以性能肯定比Redis低一些, 但是我们一般使用SSD盘来存放数据, 尽可能跟上Redis的性能。
## 适用场景
从以上的对比可以看出, 如果你的业务场景的数据比较大,Redis 很难支撑, 比如大于50G,或者你的数据很重要,不允许断电丢失,那么使用Pika 就可以解决你的问题。
而在实际使用中,Pika的性能大约是Redis的50%。
## Pika的特点
1. 容量大,支持百G数据量的存储
2. 兼容Redis,不用修改代码即可平滑从Redis迁移到Pika
3. 支持主从(slaveof)
4. 完善的运维命令
## 当前适用情况
目前Pika在线上部署并运行了20多个巨型(承载数据与Redis相比)集群
粗略的统计如下:当前每天承载的总请求量超过100亿,当前承载的数据总量约3TB
## 与Redis的性能对比
### 配置
- CPU: 24 Cores, Intel® Xeon® CPU E5-2630 v2 @ 2.60GHz
- MEM: 165157944 kB
- OS: CentOS release 6.2 (Final)
- NETWORK CARD: Intel Corporation I350 Gigabit Network Connection
### 测试过程
在 Pika 中先写入 150G 大小的数据,写入 Hash key 50 个,field 1千万级别。
Redis 写入 5G 大小的数据。
Pika:18 个线程
Redis:单线程
![](images/benchmarkVsRedis01.jpeg)
### 结论
Pika 的单线程的性能肯定不如 Redis,Pika 是多线程的结构,因此在线程数比较多的情况下,某些数据结构的性能可以优于 Redis。
## Pika 部分场景性能概况
### Pika vs SSDB ([Detail](pikaVsSSDB.md))
<img src="images/benchmarkVsSSDB01.png" height = "400" width = "480" alt="1">
<img src="images/benchmarkVsSSDB02.png" height = "400" width = "480" alt="10">
## Pika vs Redis
<img src="images/benchmarkVsRedis02.png" height = "400" width = "600" alt="2">
## 如何从Redis迁移到Pika
### 开发需要做的
开发不需要做任何事,不用改代码、不用替换driver(Pika使用原生redis的driver),什么都不用动,看dba干活就好
### DBA需要做的
1. DBA迁移Redis数据到Pika
1. DBA将Redis的数据实时同步到Pika,确保Redis与Pika的数据始终一致
1. DBA切换LVS后端ip,由Pika替换Redis
\ No newline at end of file
pika当前支持的redis接口
pika支持redis五种类型(分别为string、hash、list、set、zset)的接口,先列出其对redis的五种数据结构兼容统计。
#### 统计所用的标记含义如下:
| 图标 | 含义 |
| :--------: | :--------: |
| o | 该接口完全支持,使用方式与redis没有任何区别 |
| ! | 功能支持,但使用或输出与redis有部分差异,需注意|
|× |当前还未支持 |
---
## Keys
|接口|DEL|DUMP|EXISTS|EXPIRE|EXPIREAT|KEYS|MIGRATE|MOVE|OBJECT|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|x|o|o|o|o|x|x|x|
|接口|PERSIST|PEXPIRE|PEXPIREAT|PTTL|RANDOMKEY|RENAME|RENAMENX|RESTORE|SORT|
|状态|o|!|!|o|x|x|x|x|x|
|接口|TOUCH|TTL|TYPE|UNLINK|WAIT|SCAN|
|状态|x|o|!|x|x|!|
**备注:**
* PEXPIRE:无法精确到毫秒,底层会自动截断按秒级别进行处理;
* PEXPIREAT:无法精确到毫秒,底层会自动截断按秒级别进行处理;
* SCAN:会顺序迭代当前db的快照,由于pika允许重名五次,所以scan有优先输出顺序,依次为:string -> hash -> list -> zset -> set;
* TYPE:另外由于pika允许重名五次,所以type有优先输出顺序,依次为:string -> hash -> list -> zset -> set,如果这个key在string中存在,那么只输出sting,如果不存在,那么则输出hash的,依次类推。
* KEYS: KEYS命令支持参数支持扫描指定类型的数据,用法如 "keys * [string, hash, list, zset, set]"
## Strings
|接口|APPEND|BITCOUNT|BITFIELD|BITOP|BITPOS|DECR|DECRBY|GET|GETBIT|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|x|!|o|o|o|o|!|
|接口|GETRANGE|GETSET|INCR|INCRBY|INCRBYFLOAT|MGET|MSET|MSETNX|STRLEN|
|状态|o|o|o|o|o|o|o|o|o|o|
|接口|PSETEX|SET|SETBIT|SETEX|SETNX|SETRANGE|
|状态|o|o|!|o|o|o|
**备注:**
* BIT操作:与Redis不同,Pika的bit操作范围为2^21, bitmap的最大值为256Kb。redis setbit 只是对key的value值更新。但是pika使用rocksdb作为存储引擎,rocksdb只会新写入数据并且只在compact的时候才从硬盘删除旧数据。如果pika的bit操作范围和redis一致都是2^32的话,那么有可能每次对同一个key setbit时,rocksdb都会存储一个512M大小的value。这会产生 严重的性能隐患。因此我们对pika的bit操作范围作了取舍。
## Hashes
|接口|HDEL|HEXISTS|HGET|HGETALL|HINCRBY|HINCRBYFLOAT|HKEYS|HLEN|HMGET|HMSET|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|o|o|o|o|
|接口|HSET|HSETNX|HVALS|HSCAN|HSTRLEN|
|状态|!|o|o|o|o|
**备注:**
* HSET操作:暂不支持单条命令设置多个field value,如有需求请用HMSET
## Lists
|接口|LINDEX|LINSERT|LLEN|LPOP|LPUSH|LPUSHX|LRANGE|LREM|LSET|LTRIM|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|o|o|o|o|
|接口|RPOP|RPOPLPUSH|RPUSH|RPUSHX|BLPOP|BRPOP|BRPOPLPUSH|
|状态|o|o|o|o|x|x|x|
## Sets
|接口|SADD|SCARD|SDIFF|SDIFFSTORE|SINTER|SINTERSTORE|SISMEMBER|SMEMBERS|SMOVE|SPOP|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|o|o|o|o|
|接口|SRANDMEMBER|SREM|SUNION|SUNIONSTORE|SSCAN|
|状态|!|o|o|o|o|
**备注:**
* SRANDMEMBER:时间复杂度O( n ),耗时较多
## Sorted Sets
|接口|ZADD|ZCARD|ZCOUNT|ZINCRBY|ZRANGE|ZRANGEBYSCORE|ZRANK|ZREM|ZREMRANGEBYRANK|ZREMRANGEBYSCORE|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|o|o|o|o|
|接口|ZREVRANGE|ZREVRANGEBYSCORE|ZREVRANK|ZSCORE|ZUNIONSTORE|ZINTERSTORE|ZSCAN|ZRANGEBYLEX|ZLEXCOUNT|ZREMRANGEBYLEX|
|状态|o|o|o|o|o|o|o|o|o|o|
* ZADD 的选项 [NX|XX] [CH] [INCR] 暂不支持
## HyperLogLog
|接口|PFADD|PFCOUNT|PFMERGE|
|:-:|:-:|:-:|:-:|
|状态|o|o|o|
**备注:**
* 50w以内误差均小于1%, 100w以内误差小于3%, 但付出了时间代价.
## GEO
|接口|GEOADD|GEODIST|GEOHASH|GEOPOS|GEORADIUS|GEORADIUSBYMEMBER|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|
## Pub/Sub
|接口|PSUBSCRIBE|PUBSUB|PUBLISH|PUNSUBSCRIBE|SUBSCRIBE|UNSUBSCRIBE|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|o|o|o|o|o|o|
**备注:**
* 暂不支持keyspace notifications
## 管理命令(这里仅列出pika兼容的)
|接口|INFO|CONFIG|CLIENT|PING|BGSAVE|SHUTDOWN|SELECT|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|状态|!|o|!|o|o|o|!|
**备注:**
* info:info支持全部输出,也支持匹配形式的输出,例如可以通过info stats查看状态信息,需要注意的是key space与redis不同,pika对于key space的展示选择了分类型展示而非redis的分库展示(因为pika没有库),pika对于key space的统计是被动的,需要手动触发,然后pika会在后台进行统计,pika的key space统计是精确的。触发方式为执行:keyspace命令即可,然后pika会在后台统计,此时可以使用:keyspace readonly命令来进行查看,readonly参数可以避免反复进行统计,如果当前数据为0,则证明还在统计中;
* client:当前client命令支持client list及client kill,client list显示的内容少于redis;
* select:该命令在3.1.0版前无任何效果,自3.1.0版开始与Redis一致;
* ping: 该命令仅支持无参数使用,即使用`PING`,客户端返回`PONG`.
---
# Pika Pub/Sub文档
可用版本: >= 2.3.0
注意: 暂不支持键空间通知功能
## Pika 发布订阅命令
##### 以下为Pub/Sub发布订阅命令, 与Redis完全兼容
* PUBSUB subcommand [argument [argument ...]]
* PUBLISH channel message
* SUBSCRIBE channel [channel ...]
* PSUBSCRIBE pattern [pattern ...]
* UNSUBSCRIBE [channel [channel ...]]
* PUNSUBSCRIBE [pattern [pattern ...]]
#### 具体使用方法参考Redis的[Pub/Sub文档](http://redisdoc.com/topic/pubsub.html)
## 重要说明
* 重名问题:由于pika每个类型独立运作, 所以允许重名。例如在key abc在string中存在的时候也同样允许在hash中存在,一个key最多重名5次(5大类型),但在同一接口中是无法重名的。所以建议在使用的时候对于不同类型不要使用完全相同的key;
* 分库问题:pika自3.1.0版起支持多库,相关命令、参数的变化请参考[Pika3.1.0多库版命令、参数变化参考文档](https://github.com/Qihoo360/pika/wiki/pika3.1.0%E5%A4%9A%E5%BA%93%E7%89%88%E5%91%BD%E4%BB%A4%E3%80%81%E5%8F%82%E6%95%B0%E5%8F%98%E5%8C%96%E5%8F%82%E8%80%83)
* 数据展示:pika对于keyspace的展示选择了分类型展示而非redis的分库展示(因为pika没有分库概念),pika对于keyspace的统计是被动的,需要手动触发并不会立即输出,命令为:info keyspace [ 0|1 ],默认为0不触发,pika的keyspace统计是精确的。
#### config [get | set | rewrite]
在服务器配置中,支持参数的get、set、rewrite,支持的参数如下:
-|GET| SET
---|---|---
binlog-file-size |o| x
compact-cron |o| o
compact-interval |o| o
compression |o| x
daemonize |o| x
db-path |o| x
db-sync-path |o| x
db-sync-speed |o| x
double-master-ip |o| o
double-master-port |o| x
double-master-sid |o| x
dump-expire |o| o
dump-path |o| x
dump-prefix |o| o
expire-logs-days |o| o
expire-logs-nums |o| o
identify-binlog-type |o| o
loglevel |o| o
log-path |o| x
masterauth |o| o
max-background-compactions |o| x
max-background-flushes |o| x
max-bytes-for-level-multiplier |o| x
max-cache-files |o| x
maxclients |o| o
maxmemory |o| x
network-interface |o| x
pidfile |o| x
port |o| x
requirepass |o| o
root-connection-num |o| o
slaveof |o| x
slave-priority |o| o
slave-read-only |o| o
slotmigrate |o(<3.0.0)|o(<3.0.0)
slowlog-log-slower-than |o| o
slowlog-write-errorlog |o(<3.0.2)|o(<3.0.2)
sync-buffer-size |o| x
sync-thread-num |o| x
target-file-size-base |o| x
thread-num |o| x
timeout |o| o
userblacklist |o| o
userpass |o| o
write-buffer-size |o| x
max-cache-statistic-keys |o(<3.0.6)|o(<3.0.6)
small-compaction-threshold |o(<3.0.6)|o(<3.0.6)
databases |o(<3.1.0)|x
write-binlog |o| o
thread-pool-size |o| x
slowlog-max-len |o| o
share-block-cache |o| x
optimize-filters-for-hits |o| x
level-compaction-dynamic-level-bytes |o| x
cache-index-and-filter-blocks |o| x
block-size |o| x
block-cache |o| x
sync-window-size |o| o
### purgelogsto [write2file-name]
purgelogsto为pika原创命令, 功能为手动清理日志, 类似mysql的purge master logs to命令, 该命令有多重检测机制以确保日志一定为安全清理
### client list
与redis相比, 展示的信息少于redis
### client list order by [addr|idle]
pika原创命令,功能为按照ip address 或者 connection idle时间排序
### client kill all
pika原创命令, 功能为杀死当前所有链接(不包括同步进程但包含自己)
### 慢日志(slowlog)
与redis不同, pika的慢日志不仅存放内存中允许通过slow log命令查看,同时也允许存放在error log中并无条数限制方便接分析,但需要打开slowlog-write-errorlog参数
### bgsave
类似redis的bgsave, 先生成一个快照, 然后再将快照数据进行备份, 备份文件存放在dump目录下
### dumpoff
强行终止正在执行的dump进程(bgsave), 执行该命令后备份会立即停止然后在dump目录下生成一个dump-failed文件夹(Deprecated from v2.0)
### delbackup
删除dump目录下除正在使用(全同步中)的db快照外的其他快照
### compact
立即触发引擎层(rocksdb)所有数据结构执行全量compact操作, 全量compact能够通过sst文件的合并消除已删除或过期但未即时清理的数据, 能够在一定程度上降低数据体积, 需要注意的是, 全量compact会消耗一定io资源
### compact [string | hash | set | zset | list ]
立即触发引擎层(rocksdb)对指定数据结构执行全量compact操作, 指定数据结构的全量compact能够通过sst文件的合并消除已删除或过期但未即时清理的数据, 能够在一定程度上降低该结构数据的数据体积, 需要注意的是, 全量compact会消耗一定io资源
### compact $db [string | hash | set | zset | list ]
对指定的db进行全量compact。例如 compact db0 all会对db0上所有数据结构进行全量compact。
### flushdb [string | hash | set | zset | list ]
flushdb命令允许只清除指定数据结构的所有数据, 如需删除所有数据请使用flushall
### keys pattern [string | hash | set | zset | list ]
keys命令允许只输出指定数据结构的所有key, 如需输出所有结构的key请不要使用参数
### slaveof ip port [write2file-name] [write2file-pos] [force]
slaveof命令允许通过指定write2file(binlog)的文件名称及同步位置来实现增量同步, force参数用于触发强行全量同步(适用于主库write2file被清理无法为从库提供增量同步的场景), 全量同步后pika会自动切换至增量同步
### pkscanrange type key_start key_end [MATCH pattern] [LIMIT limit]
对指定数据结构进行正向scan, 列出处于区间 [key_start, key_end] 的Key列表(如果type为string_with_value,则列出的是key-value列表) ("", ""] 表示整个区间。
* type: 指定需要scan数据结构的类型,{string_with_value | string | hash| list | zset | set}
* key_start: 返回的起始Key, 空字符串表示 -inf(无限小)
* key_end:返回的结束Key, 空字符串表示 +inf(无限大)
### pkrscanrange type key_start key_end [MATCH pattern] [LIMIT limit]
类似于pkscanrange, 逆序
### pkhscanrange key field_start field_end [MATCH pattern] [LIMIT limit]
列出指定hash table中处于区间 [field_start, field_end] 的 field-value 列表.
* key:hash table对应的key
* field_start: 返回的起始Field, 空字符串表示 -inf(无限小)
* field_end:返回的结束Field, 空字符串表示 +inf(无限大)
### pkhrscanrange key field_start field_end [MATCH pattern] [LIMIT limit]
类似于pkhscanrange, 逆序
\ No newline at end of file
### 1 编译安装
#### Q1: 支持的系统?
A1: 目前只支持Linux环境,包括Centos,Ubuntu; **不支持Windows, Mac**
#### Q2: 怎么编译安装?
A2: 参考[编译安装wiki](https://github.com/Qihoo360/pika/wiki/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8)
#### Q3: Ubuntu编译偶尔报错`isnan isinf was not declared`?
A3: 一些旧版本的pika对Ubuntu环境兼容不好,某些情况下会出现;可以先修改代码,用`std::isnan``std::isinf`替代`isnan``isinf`, 并包含头文件`cmath`。 我们会在新版兼容这个。
```
#include <cmath>
```
### 2 设计与实现
#### Q1: 为什么要开那么多线程?比如purge,搞个定时任务不就好了。难道编程框架不支持定时器?
A1: pika有一些比较耗时的任务,如删binlog,扫描key,备份,同步数据文件等等,为了不影响正常的用户请求,这些任务都是放到后台执行的,并且将能并行的都放到不同线程里来最大程度上提升后台任务的执行速度;你说的变成框架是pink吗?pink是支持定时器的,每一个workerthread只要用户定义了cronhandle和频率,就会定时执行要执行的内容,不过这时候worker是被独占的,响应不了用户请求,所以占时的任务最好还是单独开线程去做,redis的bio也是这个原因
#### Q2: heartbeat让sender做不就好了?或者说sender有必要那么多线程吗?
A2: 这主要有两个原因,第一为了提高同步速度,sender只发不收,receiver只收不发,心跳是又单独的线程去做,如果心跳又sender来做,那么为了一秒仅有一次的心跳还要去复杂化sender和receiver的逻辑;第二其实前期尝试过合并在一起来进行连接级别的存活检测,当写入压力过大的时候会心跳包的收发会延后,导致存活检测被影响,slave误判master超时而进行不必要的重连
#### Q3: nemo存储hash的实际key,第一个字节是?header?一个类型标记?是说他是个hash类型?
A3: 的确是一个header,不过不是为了标记它是hash,因为nemo底下已经将string,hash,list,zset,set这五个数据结构分成的5个库,互不影响,之所以有header是因为一个hash有一个meta key和一堆field key,meta key对应的value记录的是这个hash的基础信息,如hash的size等等,这个header也是区分meta key和field key用的
#### Q4: list数据结构里面的curr_seq是个什么东西?
A4: list的实现是完全基于kv实现的list,通过seq来实现list类似prev和next指针,cur_seq是在meta信息里的,也就是当前已经用到那个seq了,新来的节点从这个seq开始递增接着用
#### Q5: binlog里面存储的是转化后的put,delete?还是存储的原生redis命令?
A5: 存的是redis的命令
#### Q6: rsync的deamon模式,这个rsync是linux上的rsync命令?
A6: 是的,pika前期为了更快的实现全同步的功能,此处是直接调用rsync命令来完成数据文件的收发,也是由它来进行文件的续传校验等
#### Q7: dump db文件是rocksdb本身就带的功能?具体怎么搞的?
A7: rocksdb提够对当前db快照备份的功能,我们基于此,在dump时先对pika阻住用户的写,然后记录当前的binlog偏移量并且调用rocksdb的接口来拿到当前db的元信息,这个时候就可以放开用户写,然后基于这个元信息来进行快照数据的后台拷贝,阻写的时间很短
#### Q8: 先写binlog再执行,如果这时候挂了,命令还没执行,但是写入到binlog里面了怎么办?
A8: master是先写db再写binlog,之前slave只用一个worker来同步会在master写入压力很大的情况下由于slave一个worker写入太慢而造成同步差距过大,后来我们调整结构,让slave通过多个worker来写提高写入速度,不过这时候有一个问题,为了保证主从binlog顺序一致,写binlog的操作还是只能又一个线程来做,也就是receiver,所以slave这边是先写binlog在写db,所以slave存在写完binlog挂掉导致丢失数据的问题,不过redis在master写完db后挂掉同样会丢失数据,所以redis采用全同步的办法来解决这一问题,pika同样,默认使用部分同步来继续,如果业务对数据十分敏感,此处可以强制slave重启后进行全同步即可
#### Q9: BinlogBGWorker线程之间还是要按照binlog顺序执行,这块并发能提高多少性能?
A9: 之前主从同步的差异是由主的多个worker写入而从只有一个worker写入带来的,现在的做法提高了从写db的速度,不过协议解析还是有一个线程来做,还是有瓶颈,不过这样的优化主从同步的性能提高了3~5倍左右,如果key很少的话,优化不明显,因为slave这面是通过key的hash值来sharding到其中一个worker上的
#### Q10: 秒删,每次put都要去查询key的最新版本?也就是说每次写避免伴随一次读?
A10: pika多数据结构的实现主要是“meta key + 普通key”来实现的,所以对于多数据结构的读写,肯定都是对rocksdb进行2次及以上的读写次数,你说的版本信息我们是存在meta_key中的,和其他meta信息一起被读出来,其实并没有因为版本号而额外增加读写次数
#### Q11: 为什么 Pika 使用多线程而不是像 Redis 单线程的结构?
A11: 因为 Redis 所有的操作都是对于内存的操作,因此理论上 Redis 的每次操作很短的。
#### Q12: 数据分片是在代理层做的?集合操作落在不同的槽,比如 mget,是在代理层聚合的?
A12: 目前没有对数据进行分片,你可以理解成和单机 Redis 类似,支持 master-slave 的架构,因此单个 pika 实例存储的大小的限制是磁盘大小的限制。
#### Q13: pika 支持的客户端有哪些,是否支持 pipelining?
A13: pika 支持所有的 Redis 客户端,因为 pika 设计之初就考虑到了用户的迁移成本,因此各种语言的客户端都支持。pipelining 是客户端来做的,因此我们是支持 pipelining 的。
#### Q14: 为什么不考虑 Redis cluster shard 呢?
A14: 我们开始做 pika 的时候,Redis cluster shard 还不完善,而且 Redis cluster 定位的场景和 pika 还是有区别。目前我们内部还没大范围使用 Redis cluster。
#### Q15: 不理解前面为什么加 LVS?Redis 类服务都是带状态,负载反而用吧?
A15: 我们暴露给用户的 ip 是我们 LVS 的 ip。在 Redis 前面 LVS 是为了方便主从切换,这样可以做到用户完全不感知。这里 LVS 下面挂的多个 Redis 实例,都是 master-slave 结构的。
#### Q16: 有没有对比过 ssdb,LevelDB?优势是什么?
A16: 我们公司内部有业务部门用 ssdb,目前除了游戏大部分的 ssdb 已经迁移到 pika上来。我觉得 pika 的优势在于我们代码实现的比较细,性能会比较好。
#### Q17: 存储引擎为什么没有选择 LevelDB 呢,另外市面上有类似的方案如 ssdb,有什么不同之处吗?
A17: 存储引擎上我们在 LevelDB,RocksDB 上面做过对比。LevelDB,RocksDB 在数据量比较小的时候性能差异不大,但是在数据量比较大的情况下,比如 200G 的时候,RocksDB 的性能会比 LevelDB 要来得好。但是 RocksDB 也有他的缺点,就是代码没有 LevelDB 来的那么优雅,我一直觉得一个好的 c++ 程序员看 LevelDB 代码和 effective c++ 就好了。
#### Q18: 若类似于单机 Redis,那么单机性能是个瓶颈吧?大量的客户端连接,命令处理,以及网卡流量等
A18: 是的。所以目前内部的 pika 的架构是支持一主多从、多机房自洽的方案。目前线上最多一个主 14 个从这样的结构。DBA 可以很容易的slaveof 给一个主挂上slave,然后进行数据的全同步过程。
#### Q19: pika 的多线程比 Redis 的全内存,在 get上竟然快两倍?set 也快,不存在多线程的锁消耗吗?
A19: 这里大家可以看到,这个测试结果是 pika work thread 开了 18 个。
在多数据结构的接口里面 kv 的结构的性能是最好的,而多数据结构的接口比如 hash、zset 等等就算开了 18 个线程,性能依然不如 Redis 要来得好。因为 hash、zset 等数据结构需要有抢占多数据结构元数据锁的开销,因此性能很容易下来。但是 kv 接口基本没有锁的开销。唯一的锁开销就是 RocksDB 为了实现线程安全增加的锁,因此这个结果也是可以理解了。
#### Q20: 完全是因为分布式切片不均的缘故,而放弃分布式集群吗?m-s架构每个节点不都是全量数据,占用更多资源吗?
A20: 其实我们在 bada 里面增加了多数据结构的接口,并且兼容了 Redis 的协议,但是后来用户的使用中,发现其实使用多数据结构接口的用户数据量其实不是特别大。单机 1T 的盘基本都能够承受下来。但是还是因为 Hash 分布式切片不均衡,导致我们的维护成本增加,因此我们去实现了 m-s 架构方案。
目前 bada 的方案也是和 pika 并存的方案,我们会根据用户具体的使用场景推荐使用的存储方案。我一直觉得肯定不是一套存储方案解决公司内部的所有需求,一定是某一个方案更适用于某一种存储方案。
#### Q21: 除了类比为单机 Redis 外,有没有考虑分布式支持?比如 Redis 的 sentinel 或者支持 Codis 这样可能其它 Redis 集群可以无缝迁移。
A21: Pika 目前并没有使用类似 Redis 的 sentinel,pika 前面是挂 LVS 来负责主从切换。目前也没有使用 Codis 这样的 proxy 方案。
#### Q22: 一主 14 个从?主从同步岂不是很慢?另外,从是只读的吧,读从的话,从的数据可能是过期的,数据一致性怎么解决?
A22: 一主 14 从的场景是用户的写入都是晚上定期的灌数据,读取的话从各个从库进行读取。因此这个数据一致性是用户可以接受的场景。
#### Q23:设置了expire-logs-nums(至少为10)和binlog过期时间,为何master中仍然有大量的write2file文件?
A23: pika会定期检查binlog文件,如果binlog数目超过了expire-logs-nums或者过期时间,并且所有的从节点都已经对该binlog文件进行过同步,那么binlog文件就会被删除。确认过expire-logs-nums和过期时间设置正确,可以通过info命令查看是否有从节点同步延迟比较大,导致binlog无法被删除。
##### 如果您有其他问题,请联系直接在github issue上描述您的问题,我们第一时间回复。加入QQ群294254078,我们会不定期在群里发布更新消息。
\ No newline at end of file
## Pika自版本3.1.0起开始支持多db,为了兼容多db部分命令、配置参数发生了变化,具体变化如下:
### 1.`info keyspace`命令:
**保留**
`info keyspace [1|0]`:触发统计并展示、仅展示所有db的key信息
**新增:**
`info keyspace [1|0] db0`:触发统计并展示、仅展示db0的key信息
`info keyspace [1|0] db0,db2`:触发统计并展示、仅展示db0和db2的key信息
注意:db-name仅允许使用`db[0-7]`来表示,多个db-name使用`逗号`隔开
## 2.`compact`命令:
**保留:**
`compact`:对所有db进行compact
`compact [string/hash/set/zset/list/all]`:对所有db的某个数据结构、所有数据结构进行compact
**新增:**
`compact db0 all`:仅对db0的所有数据结构进行compact
`compact db0,db2 all`:对db0及db2的所有数据结构进行compact
`compact db1 string`:仅对db1的string数据结构进行compact
`compact db1,db3 hash`:对db1及db3的hash数据结构进行compact
注意:db-name仅允许使用`db[0-7]`来表示,多个db-name使用`逗号`隔开
## 3.`slaveof`命令:
**保留:**
`slaveof 192.168.1.1 6236 [force]`:为pika实例创建同步关系,影响所有db,可通过force参数进行实例级全量同步
**删除:**
`slaveof 192.168.1.1 6236 1234 111222333`:全局创建同步关系时不再允许指定write2file文件号、write2file文件偏移量
## 4.`bgsave`命令:
**保留:**
`bgsave`:对所有db进行快照式备份
**新增:**
`bgsave db0`:仅备份db0
`bgsave db0,db3`:仅备份db0及db3
注意:db-name仅允许使用`db[0-7]`来表示,多个db-name使用`逗号`隔开
## 5.`purgelogsto`命令:
**保留:**
`purgelogsto write2file1000`:删除db0中的write2file1000以前的所有write2file
**新增:**
`purgelogsto write2file1000 db1`:删除db1中的write2file1000以前的所有write2file,每次仅允许操作一个db
注意:db-name仅允许使用`db[0-7]`来表示
## 6.`flushdb`命令:
**保留:**
`flushdb [string/hash/set/zset/list]`:删除某个db中的某个数据结构
**新增:**
`flushdb`:删除某个db中的所有数据结构
注意:与redis一致,在pika中执行flushdb前请先select到准确的db,以防误删数据
## 7.`dbslaveof`命令:
`dbslaveof db[0 ~ 7]`: 同步某一个db
`dbslaveof db[0 ~ 7] force`: 全量同步某一个db
`dbslaveof db[0 ~ 7] no one`: 停止同步某一个db
`dbslaveof db[0 ~ 7] filenum offset`: 指定偏移量同步某一个db
注意:该命令需要在两个Pika实例已经建立了主从关系之后才能对单个db的同步状态进行控制
## client kill all
删除全部的客户端
```
xxx.qihoo.net:8221> client kill all
OK
xxx.qihoo.net:8221>
```
## bgsave
执行方式和redis一样。但是异步dump完后,数据库保存在dump_path目录下,dump出来的数据库包含dump_prefix和dump时间等信息;
```
xxx.qihoo.net:8221> BGSAVE
20160422134755 : 2213: 32582292
```
返回的信息包括dump的时间(20160422134755)和当前的binlog位置,即文件号:偏移量(2213: 32582292)
```
xxx.qihoo.net # ll /data3/pika_test/dump/
总用量 0
drwxr-xr-x 1 root root 42 4月 22 13:47 pika8221-20160422
```
"/data3/pika_test/dump/"是dump的路径,"pika9221-"为dump_prefix,20160422是dump的日期
## delbackup
删除dump目录下除正在使用(全同步中)的db快照外的其他快照
```
xxx.qihoo.net:8221> DELBACKUP
OK
```
## info keyspace
执行方式是“info keyspace 1”,“info keyspace 0”和”info keyspace“, “info keyspace”和“info keyspace 0”等价;
info keyspace 1: 异步开始一次keyspace的扫描,并返回已完成的上一次keyspace扫描的结果
info keyspace 0: 返回已完成的上一次keyspace扫描的结果
```
xxx.qihoo.net:8221> info keyspace 1
# Keyspace
# Time:1970-01-01 08:00:00
kv keys:0
hash keys:0
list keys:0
zset keys:0
set keys:0
xxx.qihoo.net:8221> info keyspace
# Keyspace
# Time:2016-04-22 13:45:54
kv keys:13
hash keys:0
list keys:0
zset keys:0
set keys:0
```
## config get/set *
config get和config set的用法和redis是一样的,但是选项可能会有所不同,所以配了两个命令
1) config get *
2) config set *
用于分别列出config get和config set所能操作的选项
```
xxx.qihoo.net:8221> config get *
1) "port"
2) "thread_num"
3) "log_path"
4) "log_level"
5) "db_path"
6) "maxmemory"
7) "write_buffer_size"
8) "timeout"
9) "requirepass"
10) "userpass"
11) "userblacklist"
12) "daemonize"
13) "dump_path"
14) "dump_prefix"
15) "pidfile"
16) "maxconnection"
17) "target_file_size_base"
18) "expire_logs_days"
19) "expire_logs_nums"
20) "root_connection_num"
21) "slowlog_log_slower_than"
22) "slave-read-only"
23) "binlog_file_size"
24) "compression"
25) "db_sync_path"
26) "db_sync_speed"
xxx.qihoo.net:8221> config set *
1) "log_level"
2) "timeout"
3) "requirepass"
4) "userpass"
5) "userblacklist"
6) "dump_prefix"
7) "maxconnection"
8) "expire_logs_days"
9) "expire_logs_nums"
10) "root_connection_num"
11) "slowlog_log_slower_than"
12) "slave-read-only"
13) "db_sync_speed"
bada06.add.zwt.qihoo.net:8221>
```
## compact
因为pika底层存储引擎是基于rocksdb改造来的,会存在读写和空间放大的问题,除了rocksdb的自动compaction,pika也设置了一个手动compaction的命令,以强制compact整个kespace内的内容,支持对单个数据结构进行compact,语法为:compact [string/hash/set/zset/list/all]
```
xxx.qihoo.net:8221> compact
OK
```
一般keyspace比较大时,执行完compact命令后,占用空间会显著减小,但是耗时比较长,对读写性能也有影响,所以建议在流量不大的情况下执行
## readonly (3.1之后版本废除)
该命令用户设置服务器的写权限;执行方式为:
1)“readonly on”
2)“readonly off”
3)“readonly 1”
4)“readonly 0”
其中1)和3)等价,2)和4)等价
```
xxx.qihoo.net:8221> set a b
OK
xxx.qihoo.net:8221> get a
"b"
xxx.qihoo.net:8221> readonly 1
OK
xxx.qihoo.net:8221> set a c
(error) ERR Server in read-only
xxx.qihoo.net:8221> get a
"b"
xxx.qihoo.net:8221> readonly 0
OK
xxx.qihoo.net:8221> set a c
OK
xxx.qihoo.net:8221> get a
"c"
xxx.qihoo.net:8221>
```
### 我们根据360内部的pika使用经验及社区用户的问题反馈,整理了如下文档并会不断更新
> 为了避免以后你可能找不到本文,可点击右上方的star进行关注
**pika最佳实践之零:**
在群里提问主动带上版本号能大幅度加快问题解决速度(QQ群:294254078)
**pika最佳实践之一:**
我们建议使用3.0的最新版,如果不愿意使用3.X,请使用2.3.6,否则你会发现你遇到的很多问题都在我们的bug修复列表中。(目前2.0版本已不再维护)
**pika最佳实践之二:**
pika的线程数量建议和cpu总线程数一致,如果是单机多实例的部署,每个pika实例的线程数量可以酌情降低,但不建议低于cpu总线程数的1/2
**pika最佳实践之三:**
pika的性能和IO性能息息相关,我们不建议在机械盘上部署耗时敏感项目的pika,另外为了避免一些稀奇古怪的问题,主从服务器的硬件性能应当尽量一致
**pika最佳实践之四:**
在使用pika多数据结构的时候,尽量确保每个key中的field不要太多,建议每个key的field数量不要超过1万个,特大key可以考虑拆分为多个小key,这样可以避免超大key很多潜在的性能风险
**pika最佳实践之五:**
`root-connection-num`参数非常有用,意为“允许通过127.0.0.1登录pika的连接数”,它与最大连接数配置项`maxclients`独立,`maxclients`的用尽并不会影响`root-connection-num`,因此在发生异常`maxclients`被用尽的场景中,管理员仍然可以登录pika所在服务器并通过127.0.0.1来登入pika处理问题,避免了`maxclients`耗尽无法登录处理的尴尬局面
**pika最佳实践之六:**
`client kill`命令被加强了,如果你想一次性杀掉当前pika的所有连接,只需要执行`client kill all`,不用担心,用于同步的连接不会受到影响
**pika最佳实践之七:**
适当的调整`timeout`参数,通过该参数pika会主动断开不活动时间超过`timeout`值的连接,避免连接数耗尽问题的发生,由于连接也需要申请内存,因此合理的配置`timeout`参数也能够在一定程度上降低pika的内存占用
**pika最佳实践之八:**
pika的内存占用主要集中在sst文件的cache和连接申请内存,而通常连接申请内存会比sst的cache要大很多,pika目前已支持连接申请内存的动态调整、回收,因此连接占用的总内存大小是可以粗略估算的,如果你的pika内存占用远超预估或大于10g,那么可能是内存泄漏了,尝试依次执行命令`client kill all``tcmalloc free`来对连接内存进行强制回收,如果效果不好请升级到最新版本
**pika最佳实践之九:**
非常不建议单机运行pika,最简集群状态应为一主一从,而主从集群的容灾模式有很多种,可以考虑使用lvs、vip漂移、配置管理中间件等
**pika最佳实践之十:**
建议使用主从集群而不是双主模式,在实际使用中双主模式对使用规范的要求、网络环境要求相对更高,使用不规范、网络环境不好会造成双主模式出现问题,在出现问题后,双主模式的数据修复比主从集群数据修复复杂度要大
**pika最佳实践之十一:**
如果你的pika单机运行(非主从、主主集群),并部署在可靠的存储上,那么可以考虑通过关闭binlog(将`write-binlog`参数设置为no)来提高写入性能,不过我们并不推荐单机运行,至少应当有一个从库用于容灾,所以非单机运行pika 不建议关闭binlog
**pika最佳实践之十二:**
pika的数据目录中有大量的sst文件,这些文件随着pika数据量的增加而增加,因此你需要为pika配置一个更大的`open_file_limit`避免不够用,如果你不希望pika占用太多的文件文件描述符,可以通过适当增大单个sst的体积来降低sst的总数量,对应参数为`target-file-size-base`
**pika最佳实践之十三:**
不要修改log目录中的`write2file`文件和`manifest`,它们是同步相关的重要文件,`write2file``binlog`角色,而`manifest`则用来确保实例重启后的binlog续写及实例为从库时帮助同步中断重连后续传
**pika最佳实践之十四:**
pika的全量同步是通过rsync来进行的,因此我们提供了rsync的传输限速参数`db-sync-speed`,该参数的单位是mb,我们建议在千兆环境中该参数设置不应高于75,而在万兆环境中不应高于500,这样可以避免pika在全量同步的时候将所在服务器网卡用尽而影响到部署在服务器上的其它服务
**pika最佳实践之十五:**
在pika中执行`key *`并不会造成pika阻塞(pika是多线程的),但在存在巨量key的场景下可能会造成临时占用巨量内存(这些内存用于该连接存放`key *`的执行结果,会在`key *`执行完毕后释放),因此使用`keys *`一定要小心谨慎
**pika最佳实践之十六:**
如果发现pika有数据但`info keyspace`的显示均为0,这是因为pika并没有像Redis对key的数量做实时统计并展示,pika中key的统计需要人工触发,执行`info keyspace 1`,注意执行`info keyspace`是不会触发统计的,没有带上最后的参数`1`将会仅仅展示上一次的统计结果,key的统计是需要时间的(这是一个异步的操作),执行状态可以通过`info stats`中的`is_scaning_keyspace`进行查看,该项值为`yes`表明统计正在进行,为`no`时表明没有正在进行的统计/上一次统计已结束,在统计执行完毕前`info keyspace`不会更新,`info keyspace`的数据是存放在内存里的,重启将清零
**pika最佳实践之十七:**
不要在pika执行全量`compact`的时候触发key统计(`info keyspace 1`)或执行`keys *`,否则会造成数据体积暂时膨胀直到key统计、`keys *`执行结束
**pika最佳实践之十八:**
对存在大量过期、多数据结构内元素操作的实例配置`compact-cron`可以非常好的避免无效但还未被彻底清理的数据对性能造成的影响,或升级到3.0后打开新的key级`auto_compact`功能
如果你遇到了下面的情况,那么你的实例可能存在未及时清理的无效数据带来的性能风险:
1. 异常的数据体积(大于估算值10%以上),可以通过执行`compact`命令,在`compact`执行完毕后观察数据体积是否恢复正常
1. 请求耗时突然异常增大,可以通过执行`compact`命令,在`compact`执行完毕后观察请求耗时是否恢复正常
**pika最佳实践之十九:**
在pika3.0中我们提供了过期key的统计(可通过`info keyspace 1`来触发统计,通过`info keyspace`查看统计结果),统计结果中的`invaild_keys`的值为“已删除/过期但还未被物理删除的key的数量”,建议关注该值并在无效key数量较多时通过`compact`命令来清理,这样能够将未物理清理的无效数据控制在一个较好的程度从而确保pika的性能稳定,如果pika中存储的数据是规律性过期的,例如每个key的过期时间为7天,那么建议通过配置`compact-cron`参数来实现每天的定时全自动全量`compact``compact`会占用一定的io资源,因此如果磁盘IO压力过大,建议将其配置为业务低峰期执行,例如深夜
**pika最佳实践之二十:**
`write2file`的角色相当于`binlog`,应当根据实际写入情况调整`write2file`到合适的保留周期/数量,建议`write2file`保留周期/数量不低于48小时,足够的`write2file`能够让很多情况变得轻松,例如:大数据集群的从库扩容、从库服务器关机维修、从库迁移等等,不会因为主库`write2file`过期而被迫全量重传
**pika最佳实践之二十一:**
在主库写入量过大(普通ssd,大致写入qps大于5万)的情况下从库可能会发生同步延迟问题,可以调整从库的`sync-thread-num`参数来提高从库同步性能,该参数控制着从库的同步线程,每个线程通过hash来负责对应的key的同步,因此主库写入操作的不同的key的数量越多该参数的效果就会越好,而如果巨量的写入仅集中在几个key中,那么该参数可能无法达到预期效果
**pika最佳实践之二十二:**
pika的备份生成为快照式,通过硬链接存放在dump目录中以日期为后缀,备份每天只能生成一份,多次生成备份时新的备份会覆盖之前的。在生成备份快照的时候,为了确保数据的一致性pika会暂时阻塞写入,阻塞时间与实际数据量相关,根据测试500g的pika生成备份快照也仅需50ms,在写入阻塞的过程中连接不会中断请求不会异常,但client会感觉到“在那一瞬间请求耗时增加了一些”。由于pika的快照是db目录中sst文件的硬连接,因此最初这个目录是不会占用磁盘空间的,而在pika db目录中的sst文件发生了合并、删除后,硬链接会因为其特性而体现真实体积从而开始占用磁盘空间,所以请根据实际的磁盘空间调整备份保留天数,避免备份太多而造成磁盘空间用尽
**pika最佳实践之二十三:**
如果写入量巨大且磁盘性能不足以满足rocksdb memtable的及时刷盘需求,那么rocksdb很可能会进入写保护模式(写入将被全部阻塞),对于该问题我们建议更换性能更好的存储来支撑,或者降低写入频率(例如将集中写数据的2小时拉长到4小时),也可适当加大`write-buffer-size`的值来提高memtable的总容量从而降低整个memtable被写满的可能,但实际根据测试发现修改该参数并不能彻底解决该问题,因为“写的memtable迟早要刷下去的!之前刷不动,现在也刷不动!”
**pika最佳实践之二十四:**
pika对数据进行了压缩,默认压缩算法为`snappy`,并允许改为`zlib`,因此每一次数据的存入、读出都需要经过压缩、解压,这对cpu有一定的消耗,非常建议像使用Redis一样使用pika:在pika中关闭压缩,而在client中完成数据的压缩、解压,这样不仅能够降低数据体积,还能有效降低pika的cpu压力,如果你的存储空间不是问题但并不想调整client,可以关闭压缩来降低cpu压力,代价是磁盘占用的增加,注意关闭、开启压缩需要重启实例但无需重做数据
**pika最佳实践之二十五:**
读写分离很重要,pika在常见的主从集群中由于写入是单点的(主库),因此写入性能是有极限的,而读取可以通过多个从库来共同支撑,因此pika集群的读取性能是随着从库数量的增加而增加的,所以对于读取量很大的场景,建议在业务层代码加入读写分离策略同时在pika层增加从库数量通过多个从库来提供读服务,这样能够大幅度提高集群稳定性并有效降低读耗时
**pika最佳实践之二十六:**
全量compact的原理是逐步对rocksdb的每一层做数据合并、清理工作,在这个过程中会新增、删除大量的sst文件,因此在执行全量`compact`的时候可以发现数据体积先增大后减小并最终减小到一个稳定值(无效、重复数据合并、清理完毕仅剩有效数据),建议在执行`compact`前确保磁盘空余空间不低于30%避免新增sst文件时将磁盘空间耗尽,另外pika支持对指定数据结构进行`compact`,例如一个实例中已知hash结构的无效数据很少但hash结构数据量很大,set结构数据量很大且无效数据很多,在这个例子中hash结构的`compact`是没有必要的,你可以通过`compact set`实现仅仅对set结构的`compact`
**pika最佳实践之二十七:**
备份是以硬链接db目录中的sst的方式产生的,因此在存在备份文件的情况下,一旦执行全量compact由于pika db目录中的所有sst都会被`compact`“清洗”一遍(逐步将所有老的sst删除替换成新的sst),这将造成备份硬链接文件的体积变为真实体积,极端情况下备份文件会额外占用一倍的空间,因此如果你的磁盘空余空间不大,那么在执行全量`compact`之前最好删除备份
**pika最佳实践之二十八:**
pika和Redis一样支持慢日志功能并可通过`slowlog`命令查看,但我们知道`slowlog`的存储是有上限的,这个上限取决于你的配置,如果配置过大会造成`slowlog`占用太多内存,而pika允许将慢日志记录到`pika.ERROR`日志中用于追溯、分析,该功能需要将`slowlog-write-errorlog`设置为yes
**pika最佳实践之二十九:**
pika没有提供Redis的命令改名(`rename-command`)功能,因为部分命令的改名会造成一些工具、中间件的工作异常(例如将config改名后哨兵会无法工作),因此pika额外增加了`userpass``userblacklist`来解决这一问题。`userpass`对应`requirepass`,使用`userpass`登录的用户会受到`userblacklist`的限制,它们无法执行配置在`userblacklist`中的命令,而`requirepass`则不受影响,可以简单的将通过`requirepass`登录pika的用户理解为“超级用户”,将通过`userpass`登录pika的用户理解为“普通用户”,我们非常建议pika运维将`userpass`提供给业务用于代码访问并在`userblacklist`增加例如`slaveof`,`config`,`shutdown`,`bgsave`,`dumpoff`,`client`,`keys`等管理类、风险性命令来避免误操作造成的故障
**pika最佳实践之三十:**
Pika在3.0.7版本对网络库进行了改造,原先网络通信以及数据查询插入操作都在配置文件中`thread-num`对应的线程中执行,改造过后网络通信还是在`thread-num`对应线程中执行,而数据写入和删除操作由配置文件中`thread-pool-size`控制的线程池中执行,用户可以根据自己的场景对这两个参数进行调整,如果客户端执行的是一些比较重的操作,可以适量将`thread-pool-size`调大,一般情况下我们建议`thread-pool-size = 2 * thread-num`
**pika最佳实践之三十一:**
Pika从3.0.5开始提供了更细致的compact策略,该策略允许对key的操作进行监控并在达到设置阈值的时候对该key进行单独的compact,这个功能仅对hash,set,zset,list这四种含有field的数据结构生效,对应的参数为:
* `max-cache-statistic-keys` 设置受监控key的数量,例如10000(监控1万个key)
* `small-compaction-threshold` 设置操作阈值(该key中有多少个field被修改或该key中的filed被修改超多多少次),例如500
该功能特别适合(包括并不限于)以下或类似的使用场景,它能够将这些场景中的无效数据及时的清除从而确保性能的持续稳定:
1. 大量hash结构数据,频繁的修改、新增、删除对应key中的数据
2. 大量list结构,以类似队列的方式使用pika
3. 经常需要对多数据结构类型的key直接删除然后复用
**pika最佳实践之三十二:**
在业务压力大、IO使用率高的时候尽量不要执行`compact`,因为在IO资源不足时`compact`过程中带来的IO消耗可能会与业务请求发生IO争抢,使实例整体性能下降,如果不小心执行了也没有关系,重启pika即可,数据不会损坏不会丢失,上一次未完成的`compact`残留的sst文件会被rocksdb自动安全的全部清理
**pika最佳实践之三十三:**
在建立主从的时候发现从库反复的进行全同步很可能是由于主库写入量太大,在全同步完毕期间清理掉了原先dumpdb时的binlog点位,导致从库在替换了db之后重新trysync发现点位又找不到,然后重新触发全同步,陷入了循环,这种场景下可以动态将主库的expire-logs-nums调大,让其保留尽量多的binlog, 建立主从成功之后再调节回来即可
---
_不断更新_
\ No newline at end of file
此差异已折叠。
```
# pika 端口
port : 9221
# pika是多线程的, 该参数能够配置pika的线程数量, 不建议配置值超过部署服务器的CPU核心数量
thread-num : 1
# 处理命令用户请求命令线程池的大小
thread-pool-size : 8
# sync 主从同步时候从库执行主库传递过来命令的线程数量
sync-thread-num : 6
# sync 处理线程的任务队列大小, 不建议修改
sync-buffer-size : 10
# Pika日志目录, 用于存放INFO, WARNING, ERROR日志以及用于同步的binlog(write2fine)文件
log-path : ./log/
# Pika数据目录
db-path : ./db/
# Pika 底层单个rocksdb单个memtable的大小, 设置越大写入性能越好但会在buffer刷盘时带来更大的IO负载, 请依据使用场景合理配置
[RocksDb-Tuning-Guide](https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide)
write-buffer-size : 268435456
# Pika 的连接超时时间配置, 单位为秒, 当连接无请求时(进入sleep状态)开始从配置时间倒计时, 当倒计时为0时pika将强行
# 断开该连接, 可以通过合理配置该参数避免可能出现的pika连接数用尽问题, 该参数默认值为60
timeout : 60
# 密码管理员密码, 默认为空, 如果该参数与下方的userpass参数相同(包括同时为空), 则userpass参数将自动失效, 所有用户均为
# 管理员身份不受userblacklist参数的限制
requirepass : password
# 同步验证密码, 用于slave(从库)连接master(主库)请求同步时进行验证, 该参数需要与master(主库)的requirepass一致
masterauth :
# 用户密码, 默认为空, 如果该参数与上方的userpass参数相同(包括同时为空), 则本参数将自动失效, 所有用户均为管理员身份不
# 受userblacklist参数的限制
userpass : userpass
# 指令黑名单, 能够限制通过userpass登录的用户, 这些用户将不能使用黑名单中的指令, 指令之间使用","隔开, 默认为空
# 建议将高风险命令配置在该参数中
userblacklist : FLUSHALL, SHUTDOWN, KEYS, CONFIG
# 分为经典模式和分片模式,[classic | sharding],经典模式中支持多db的配置
instance-mode : classic
# 经典模式下下指定db的数量,使用方式和redis一致
databases : 1
# 分片模式下每一个table中默认的slot数量
default-slot-num:16
# 定义一个副本组又多少个从副本,目前支持的配置选项范围[0, 1, 2, 3, 4], 0代表不开启此功能
replication-num : 0
# 定义在返回客户端之前主副本收到多少个从副本的ACK反馈信息。目前可以配置的选项范围[0, ...replicaiton-num],0代表不开启此功能。
consensus-level : 0
# Pika的dump文件名称前缀, bgsave后生成的文件将以该前缀命名
dump-prefix : backup-
# 守护进程模式 [yes | no]
daemonize : yes
# slotmigrate [yes | no], pika3.0.0暂不支持该参数
#slotmigrate : no
# Pika dump目录设置, bgsave后生成的文件将存放在该目录中
dump-path : /data1/pika9001/dump/
# dump目录过期时间, 单位为天, 默认为0即永不过期
dump-expire: 0
# pidfile Path pid文件目录
pidfile : /data1/pika9001/pid/9001.pid
# pika最大连接数配置参数
maxclients : 20000
# rocks-db的sst文件体积, sst文件是层级的, 文件越小, 速度越快, 合并代价越低, 但文件数量就会超多, 而文件越大, 速度相对变慢, 合并代价大, 但文件数量会很少, 默认是 20M
target-file-size-base : 20971520
# binlog(write2file)文件保留时间, 7天, 最小为1, 超过7天的文件会被自动清理
expire-logs-days : 7
# binlog(write2file)文件最大数量, 200个, 最小为10, 超过200个就开始自动清理, 始终保留200个
expire-logs-nums : 200
# root用户连接保证数量:2个, 即时Max Connection用完, 该参数也能确保本地(127.0.0.1)有2个连接可以同来登陆pika
root-connection-num : 2
# 慢日志记录时间, 单位为微秒, pika的慢日志记录在pika-ERROR.log中, pika没有类似redis slow log的慢日志提取api
slowlog-log-slower-than : 10000
# slave是否是只读状态(yes/no, 1/0)
# slave-read-only : 0
# Pika db 同步路径配置参数
db-sync-path : ./dbsync/
# 该参数能够控制全量同步时的传输速度, 合理配置该参数能够避免网卡被用尽, 该参数范围为1~1024, 意为:1MB/s~1024MB/s,当该参数
# 被配置为小于0或大于1024时, 该参数会被自动配置为1024
db-sync-speed : -1 (1024MB/s)
# 指定网卡
# network-interface : eth1
# 同步参数配置, 适用于从库节点(slave), 该参数格式为ip:port, 例如192.168.1.2:6666, 启动后该示例会自动向192.168.1.2的
# 6666端口发送同步请求
# slaveof : master-ip:master-port
# 配置双主或Hub需要的server id, 不使用双主或Hub请忽略该参数
server-id : 1
# 双主配置, 不使用双主请忽略以下配置
double-master-ip : 双主对端Ip
double-master-port : 双主对端Port
double-master-server-id : 双主对端server id
# 自动全量compact, 通过配置的参数每天定时触发一次自动全量compact, 特别适合存在多数据结构大量过期、删除、key名称复用的场景
# 参数格式为:"启动时间(小时)-结束时间(小时)/磁盘空余空间百分比", 例如你需要配置一个每天在凌晨3点~4点之间自动compact的任务
# 同时该任务仅仅在磁盘空余空间不低于30%的时候执行, 那么应配置为:03-04/30, 该参数默认为空
compact-cron :
# 自动全量compact, 该参与与compact-cron的区别为, compact-cron每天仅在指定时间段执行, 而compact-interval则以配置时间为周期
# 循环执行, 例如你需要配置一个每4小时执行一次的自动compact任务, 同时该任务仅仅在磁盘空余空间不低于30%的时候执行, 那么该参
# 数应配置为:4/30, 该参数默认为空
compact-interval :
# 从库实例权重设置, 仅配合哨兵使用,无其它功能, 权重低的slave会优先选举为主库, 该参数默认为0(不参与选举)
slave-priority :
# 该参数仅适用于pika跨版本同步时不同版本的binlog能够兼容并成功解析, 该参数可配置为[new | old]
# 当该参数被配置为new时, 该实例仅能作为3.0.0及以上版本pika的从库, 与pika2.3.3~2.3.5不兼容
# 当该参数被配置为old时, 该时候仅能作为2.3.3~2.3.5版本pika的从库, 与pika3.0.0及以上版本不兼容
# 该参数默认值为new, 该参数可在没有配置同步关系的时候通过config set动态调整, 一旦配置了同步关系则不可动态修改
# 需要先执行slaveof no one关闭同步配置, 之后即可通过config set动态修改
identify-binlog-type : new
# 主从同步流量控制的的窗口,主从高延迟情形下可以通过提高该参数提高同步性能。默认值9000最大值90000。
sync-window-size : 9000
# 处理客户端连接请求的最大缓存大小,可配置的数值为67108864(64MB) 或 268435456(256MB) 或 536870912(512MB)
# 默认是268435456(256MB),需要注意的是主从的配置需要一致。
# 单条命令超过此buffer大小,服务端会自动关闭与客户端的连接。
max-conn-rbuf-size : 268435456
###################
#Critical Settings#
# 危险参数 #
###################
# write2file文件体积, 默认为100MB, 一旦启动不可修改, limited in [1K, 2G]
binlog-file-size : 104857600
# 压缩方式[snappy, zlib, lz4, zstd]默认为snappy, 一旦启动不可修改
# 官方发布的二进制提供默认的snaapy的静态连接。如果需要其他压缩方式请自行下载相应静态库并进行编译。
compression : snappy
# 指定后台flush线程数量, 默认为1, 范围为[1, 4]
max-background-flushes : 1
# 指定后台压缩线程数量, 默认为1, 范围为[1, 4]
max-background-compactions : 1
# DB可以使用的打开文件的数量, 默认为5000
max-cache-files : 5000
# pika实例所拥有的rocksdb实例使用的memtable大小上限,如果rocksdb实际使用超过这个数值,下一次写入会造成刷盘
[Rocksdb-Basic-Tuning](https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning)
max-write-buffer-size : 10737418240
# 限制命令返回数据的大小,应对类似于keys *等命令,返回值过大将内存耗尽。
max-client-response-size : 1073741824
# pika引擎中层级因子, 用于控制每个层级与上一层级总容量的倍数关系, 默认为10(倍), 允许调整为5(倍)
max-bytes-for-level-multiplier : 10
# 统计对于key的操作次数,对于操作频繁的一部分key做小规模compaction
# max-cache-statistic-keys 为受监控key的数量,配置为0代表关闭此功能
max-cache-statistic-keys : 0
# 如果开启小规模compaction,如果对于key操作次数超过small-compaction-threshold上限,那么对该key进行compaction
small-compaction-threshold : 5000
```
#### db目录
用于存放pika的所有数据文件,包含5个子目录(5大数据类型)它们是:kv,set,zset,hash,list,从pika3.0.0开始,这些数据结构目录为:hashes,lists,sets,strings,zsets
#### log目录
用于存放所有日志文件,包含:一般日志、警告日志、错误日志、同步日志(binlog)、同步日志节点信息文件(mainfest)
#### dump目录
用于存放快照式备份产生的文件
#### pid目录
用于存放pika的pid文件
#### dbsync目录
用于主从全量同步时存放全量同步所需的文件
\ No newline at end of file
# Pika双主文档
可用版本: 2.3.0 ~ 3.0.11
## 介绍
为了避免额外的维护成本, Pika在之前主从的逻辑设计上支持了双主架构, 通过`conf`文件配置双主模式. 在双主建立成功的模式下, 仍然可以通过`slaveof`为两个`master`增加`slave`节点。
## 使用方法
* 通过`pika.conf`文件配置双主模式
修改双主A的`pika.conf`配置文件
...
# server-id for hub
server-id : 1
...
# The peer-master config
double-master-ip : 192.168.10.2 配置另一个主的ip
double-master-port : 9220 配置另一个主的port
double-master-server-id : 2 配置另一个主的server id (注意不要与本机server id和已经连接的slave的sid重复)
修改双主B的`pika.conf`配置文件
...
# server-id for hub
server-id : 2
...
# The peer-master config
double-master-ip : 192.168.10.1 配置另一个主的ip
double-master-port : 9220 配置另一个主的port
double-master-server-id : 1 配置另一个主的server id (注意不要与本机server id和已经连接的slave的sid重复)
* 分别启动两个Pika实例, 使用`info`查看信息
查看`192.168.10.1`的`info`信息
...
# DoubleMaster(DOUBLEMASTER)
role:double_master
the peer-master host:192.168.10.2
the peer-master port:9220
double_master_mode: True
repl_state: 3
double_master_server_id:2
double_master_recv_info: filenum 0 offset 0
其中`repl_state`为`3`代表双主连接建立成功
## 双主启动时binlog偏移校验失败
* 双主分别启动时会检验对端实例的`binlog`偏移量, 如果两端`binlog`偏移量相差过大会造成双主连接建立失败.
`IOFO`级别日志会报
pika_admin.cc:169] Because the invalid filenum and offset, close the connection between the peer-masters
利用`info`命令查看实例信息
# DoubleMaster(MASTER)
role:master
the peer-master host:192.168.10.2
the peer-master port:9220
double_master_mode: True
repl_state: 0
double_master_server_id:2
double_master_recv_info: filenum 0 offset 0
其中`repl_state`为`0`代表双主建立失败
* 假如双主建立失败, 无需停机可以通过`slaveof`命令以某一个双主实例的为基准做数据全同步
例如`192.168.10.2`以`192.168.10.1`的数据做全同步
slaveof 192.168.10.1
## 断开双主连接
双主的两个节点分别执行`slaveof no one`用来断开连接.
## 测试场景以及测试结果
|-|场景|测试结果|备注|
|:-:|:-:|:-:|:-:|
|正确性测试|
|1|双主单边写入|测试两次,分别使用12和64线程,key数量30w-100w不等,未发现key丢失,校验value相等|通过|
|2|双主同时写入(不操作相同key)|测试3次,key数量60w左右,两边key数量相同,校验value相等|通过|
|3|双主同时写入(相同key)|测试2次,key数量40w左右,两边key数量相同,校验value相等|通过|
|4|双主同时写入(不操作相同key),使用iptables模拟网络延迟|测试3次,key数量60w左右,两边key数量相同,校验value相等|通过|
|5|双主同时写入(相同key),使用iptables模拟网络延迟|测试2次,key数量40w左右,两边key数量相同, 但部分key的value不同|通过|
|6|双主同时写入(相同key),挂载两个从库|key数量20w左右,两边key数量一致,值也相同|通过|
|运维测试|
|7|单主写入,shutdown其中一个主库,然后挂载新从库重新搭建双主|key数量100w左右,两边key数量一致,值也相同|通过|
|8|单主写入,重启其中一个主库|key数量100w左右,两边key数量一致,值也相同|通过|
## FAQ
* 双主模式下如何避免数据循环传播?
> 每个双主实例内部会维护另一个`master`的信息, 通过`binlog`中的`server id`字段来判断是否需要同步给另一个`master`
* 如何关闭双主模式?
> 双主关系可以随时取消, 只需要把其中一个节点停止即可
* 双主模式下是否支持双写?
> 双主模式下支持双写
* 双主的两个实例能否同时操作更新同一个key?
> 双主模式下操作同一个key, 不保证value的最终一致
* 双主模式下如何增加`slave`节点
> 使用正常的主从配置方法即可
* 双主A,B 因为意外情况,B实例因为宕机过久, 重启后因为`binlog`偏移相差过大双主模式建立失败, 怎么恢复?
> 以A实例为基准做全同步, 直接利用`slaveof`命令做全同步后会自动恢复双主连接
执行`INFO`命令
```
127.0.0.1:9221> info [section]
```
```
#主库:
# Server
pika_version:2.3.0 -------------------------------------------- pika 版本信息
pika_git_sha:3668a2807a3d047ea43656b58a2130c1566eeb65 --------- git的sha值
pika_build_compile_date: Nov 14 2017 -------------------------- pika的编译日期
os:Linux 2.6.32-2.0.0.8-6 x86_64 ------------------------------ 操作系统信息
arch_bits:64 -------------------------------------------------- 操作系统位数
process_id:12969 ---------------------------------------------- pika pid信息
tcp_port:9001 ------------------------------------------------- pika 端口信息
thread_num:12 ------------------------------------------------- pika 线程数量
sync_thread_num:6 --------------------------------------------- sync线程数量
uptime_in_seconds:3074 ---------------------------------------- pika 运行时间(秒)
uptime_in_days:0 ---------------------------------------------- pika 运行时间(天)
config_file:/data1/pika9001/pika9001.conf --------------------- pika conf文件位置
server_id:1 --------------------------------------------------- pika的server id
# Data
db_size:770439 ------------------------------------------------ db的大小(Byte)
db_size_human:0M ---------------------------------------------- 人类可读的db大小(M)
compression:snappy -------------------------------------------- 压缩方式
used_memory:4248 ---------------------------------------------- 使用的内存大小(Byte)
used_memory_human:0M ------------------------------------------ 人类可读的使用的内存大小(M)
db_memtable_usage:4120 ---------------------------------------- memtable的使用量(Byte)
db_tablereader_usage:128 -------------------------------------- tablereader的使用量(Byte)
# Log
log_size:110174 ----------------------------------------------- binlog的大小(Byte)
log_size_human:0M --------------------------------------------- 人类可读的binlog大小(M)
safety_purge:none --------------------------------------------- 目前可以安全删除的文件名
expire_logs_days:7 -------------------------------------------- 设置binlog过期天数
expire_logs_nums:10 ------------------------------------------- 设置binlog过期数量
binlog_offset:0 388 ------------------------------------------- binlog偏移量(文件号,偏移量)
# Clients
connected_clients:2 ------------------------------------------- 当前连接数
# Stats
total_connections_received:18 --------------------------------- 总连接次数统计
instantaneous_ops_per_sec:1 ----------------------------------- 当前qps
total_commands_processed:633 ---------------------------------- 请求总计
is_bgsaving:No, , 0 ------------------------------------------- pika 备份信息:是否在备份,备份名称,备份
is_scaning_keyspace:No ---------------------------------------- 是否在执行scan操作
is_compact:No ------------------------------------------------- 是否在执行数据压缩操作
compact_cron: ------------------------------------------------- 定时compact(format: start-end/ratio, eg. 02-04/60)
compact_interval: --------------------------------------------- compact的间隔(format: interval/ratio, eg. 6/60)
# CPU
used_cpu_sys:48.52 -------------------------------------------- Pika进程系统CPU占用时间
used_cpu_user:73.10 ------------------------------------------- Pika进程用户CPU占用时间
used_cpu_sys_children:0.05 ------------------------------------ Pika子进程系统CPU占用时间
used_cpu_user_children:0.05 ----------------------------------- Pika子进程用户CPU占用时间
# Replication(MASTER)
role:master --------------------------------------------------- 本实例角色
connected_slaves:1 -------------------------------------------- 当前从库数量
slave0:ip=192.168.1.1,port=57765,state=online,sid=2,lag=0 ----- lag:当前主从binlog相差的字节数(byte),如果有多个从库则依次展示
#从库(区别仅在于同步信息的展示):
# Replication(SLAVE)
role:slave ---------------------------------------------------- 本实例角色
master_host:192.168.1.2 --------------------------------------- 主库IP
master_port:9001 ---------------------------------------------- 主库端口
master_link_status:up ----------------------------------------- 当前同步状态
slave_read_only:1 --------------------------------------------- 从库是否readonly
repl_state: connected ----------------------------------------- 从库同步连接的当前状态
# Keyspace(key数量展示,按照数据类型分类展示,默认不更新,仅在执行info keyspace 1的时候刷新该信息)
# Time:2016-04-22 17:08:33 ------------------------------------ 上一次统计的时间
db0 Strings_keys=100004, expires=0, invaild_keys=0
db0 Hashes_keys=2, expires=0, invaild_keys=0
db0 Lists_keys=0, expires=0, invaild_keys=0
db0 Zsets_keys=1, expires=0, invaild_keys=0
db0 Sets_keys=0, expires=0, invaild_keys=0
# keys:当前有效KEY的数量,等同于redis的keys
# expires:keys中带有expire属性的key的数量,等同于Redis
# invalid_keys:pika独有,指已经失效(标记删除),但还未被rocksdb彻底物理删除的key,虽然这些key不再会被访问到,但会占用一定磁盘空间,如果发现较大可以通过执行compact来彻底清理
# DoubleMaster(MASTER)
role:master --------------------------------------------------- 双主角色
the peer-master host: ----------------------------------------- 双主对端IP
the peer-master port:0 ---------------------------------------- 双主对端Port
the peer-master server_id:0 ----------------------------------- 双主对端server id
double_master_mode: False ------------------------------------- 是否配置双主模式
repl_state: 0 ------------------------------------------------- 双主连接状态
double_master_recv_info: filenum 0 offset 0 ------------------- 从对端接受的Binlog偏移量
```
## 快速试用
如果想快速试用pika,目前提供了Centos5,Centos6和Debian(Ubuntu16) binary版本,可以在[release页面](https://github.com/Qihoo360/pika/releases)看到,具体文件是pikaX.Y.Z_xxx_bin.tar.gz。
```bash
1. unzip file
$ tar zxf pikaX.Y.Z_xxx_bin.tar.gz
2. change working directory to output
note: we should in this directory, caz the RPATH is ./lib;
$ cd output
3. run pika:
$ ./bin/pika -c conf/pika.conf
```
## 编译安装
### CentOS (Fedora, Redhat)
* 安装必要的lib
```bash
$ sudo yum install gflags-devel snappy-devel glog-devel protobuf-devel
```
* 可选择的lib
```
$ sudo yum install zlib-devel lz4-devel libzstd-devel
```
* 安装gcc
```
$ sudo yum install gcc-c++
```
* 如果机器gcc版本低于4.8,需要切换到gcc4.8或者以上,下面指令可临时切换到gcc4.8
```
$ sudo wget -O /etc/yum.repos.d/slc6-devtoolset.repo http://linuxsoft.cern.ch/cern/devtoolset/slc6-devtoolset.repo
$ sudo yum install --nogpgcheck devtoolset-2
$ scl enable devtoolset-2 bash
```
* 获取项目源代码
```
$ git clone https://github.com/OpenAtomFoundation/pika.git
```
* 更新依赖的子项目
```
$ cd pika
$ git submodule update --init
```
* 切换到最新release版本
```
a. 执行 git tag 查看最新的release tag,(如 v2.3.1)
b. 执行 git checkout TAG切换到最新版本,(如 git checkout v2.3.1)
```
* 编译
```
$ make
```
> note: 若编译过程中,提示有依赖的库没有安装,则有提示安装后再重新编译
### Debian (Ubuntu)
* 安装必要的lib
```bash
$ sudo apt-get install libgflags-dev libsnappy-dev
$ sudo apt-get install libprotobuf-dev protobuf-compiler
$ sudo apt install libgoogle-glog-dev
```
* 获取项目源代码
```bash
$ git clone https://github.com/OpenAtomFoundation/pika.git
$ cd pika
```
* 切换到最新release版本
```
a. 执行 git tag 查看最新的release tag,(如 v2.3.1)
b. 执行 git checkout TAG切换到最新版本,(如 git checkout v2.3.1)
```
* 编译
```
$ make
```
> note: 若编译过程中,提示有依赖的库没有安装,则有提示安装后再重新编译
### [静态编译方法](https://github.com/OpenAtomFoundation/pika/issues/1148)
## 使用
```
$ ./output/bin/pika -c ./conf/pika.conf
```
## 注意
启动出现 Attempt to free invalid pointer 问题请尝试升级tcmalloc 版本,建议使用gperftools 2.7 版本包含的tcmalloc。
\ No newline at end of file
### Pika内存占用
1. rocksdb 内存占用
2. pika 内存占用(tcmalloc 占用)
#### 1. rocksdb 内存占用
命令行命令 info data
used_memory_human = db_memtable_usage + db_tablereader_usage
相应配置及对应影响参数
write-buffer-size => db_memtable_usage
max-write-buffer-size => db_memtable_usage
max-cache-files => db_tablereader_usage
对应rocksdb配置解释
https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
https://github.com/facebook/rocksdb/wiki/Memory-usage-in-RocksDB
#### 2. pika 内存占用
如果使用tcmalloc,绝大多数情况下是tcmalloc暂时占用内存。
命令行命令:tcmalloc stats
命令行命令:tcmalloc free 释放tcmalloc 占用内存
\ No newline at end of file
#pika与ssdb性能对比
## 测试环境
相同配置服务端、客户机各1台:
CPU: 24 Cores, Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
MEM: 198319652 kB
OS: CentOS release 6.2 (Final)
NETWORK CARD: Intel Corporation I350 Gigabit Network Connection
## 测试接口
set和get
## 测试方法
使用ssdb和pika默认配置(pika配16个worker),客户机执行 ./redis-benchmark -h ... -p ... -n 1000000000 -t set,get -r 10000000000 -c 120 -d 200,通过set和get接口分别对ssdb和pika进行10亿次写入+10亿次读取
## 结果图表
<img src="images/benchmarkVsSSDB01.png" height = "400" width = "480" alt="1">
<img src="images/benchmarkVsSSDB02.png" height = "400" width = "480" alt="10">
## 详细测试结果(10亿)
### Set:
ssdb
1000000000 requests completed in 25098.81 seconds
0.52% <= 1 milliseconds
96.39% <= 2 milliseconds
97.10% <= 3 milliseconds
97.34% <= 4 milliseconds
97.57% <= 5 milliseconds
98.87% <= 38 milliseconds
...
99.99% <= 137 milliseconds
100.00% <= 215 milliseconds
...
100.00% <= 375 milliseconds
39842.53 requests per second
pika
1000000000 requests completed in 11890.80 seconds
18.09% <= 1 milliseconds
93.32% <= 2 milliseconds
99.71% <= 3 milliseconds
99.86% <= 4 milliseconds
99.92% <= 5 milliseconds
99.94% <= 6 milliseconds
99.96% <= 7 milliseconds
99.97% <= 8 milliseconds
99.97% <= 9 milliseconds
99.98% <= 10 milliseconds
99.98% <= 11 milliseconds
99.99% <= 12 milliseconds
...
100.00% <= 19 milliseconds
...
100.00% <= 137 milliseconds
84098.66 requests per second
### Get:
ssdb
1000000000 requests completed in 12744.41 seconds
7.32% <= 1 milliseconds
96.08% <= 2 milliseconds
99.49% <= 3 milliseconds
99.97% <= 4 milliseconds
99.99% <= 5 milliseconds
100.00% <= 6 millisecondsj
...
100.00% <= 229 milliseconds
78465.77 requests per second
pika
1000000000 requests completed in 9063.05 seconds
84.97% <= 1 milliseconds
99.76% <= 2 milliseconds
99.99% <= 3 milliseconds
100.00% <= 4 milliseconds
...
100.00% <= 33 milliseconds
110338.10 requests per second
## 测试结论
由于pika的设计支持多线程的读写(ssdb仅支持1个线程写,多个线程读),在本测试环境中,pika写性能是ssdb的2.1倍(qps:84098 vs 39842),读性能是ssdb的1.4倍(qps:110338 vs 78465)
ssdb 写延迟分布 0.52%在1ms以内, 97.10%在3ms以内, 而pika 的写延迟分布18.32%在1ms以内, 99.71%在3ms以内. 说明 pika 的写延迟在写入量相同的情况下(10亿)下明显优于ssdb
ssdb 读延迟分布7.32%在1ms以内, 99.49%在3ms以内, 而pika 的84.97%在1ms以内, 99.99%在3ms以内. 说明 pika 的读延迟在读取量相同的情况(10亿)下优于ssdb
所以在本测试环境中,pika 各个方面的性能都优于ssdb
## 注
1. ssdb的写性能开始还不错(8w\~9w),随着写入的持续,速度逐渐降到3w左右,然后一直持续到写入结束;pika的写入速度则相对稳定,全程维持在8w\~9w
2. 本测试结果仅供参考,大家在使用pika前最好根据自己的实际使用场景来进行针对性的性能测试
关于sharding mode,pika底层提供slot 的概念。Pika将key进行哈希取模之后散列到各个slot当中处理。sharding mode 根据线上的具体情况可以应用于单个pika,也可以应用到多个pika组成的pika cluster。这个tutorial主要介绍开启sharding mode 需要了解的一些概念,以及需要调整的一些配置参数。
#### 0. 模式的选择
目前pika 分为两种模式,两种模式不可兼容,所以请一定先根据业务确定使用哪一种模式。
一种是经典模式(classic),经典模式下可以支持绝大多数的业务压力,同时支持8个db(db0-db7)的并发读写。建议一般的业务线可以先压测这个模式。
如果经典模式不能满足线上巨大的压力,可以尝试另一种模式,集群模式(sharding),相对于经典模式,集群模式会提供更高的QPS,同时也会占用更多的硬件资源。以Codis下配置pika为其后端存储为例,codis 默认是集群slots总数为1024,每个slots 都提供五种数据结构的读写。对应每一种数据结构,pika都会起rocksdb实例。这样,集群需要起1024*5个rocksdb实例,集群模式的意义当然是把这些5120个pika实例分布在各个机器的pika实例上。所以我们推荐集群需要一定规模的物理机,这个样每个物理机上承载的rocksdb实例不会太多。当然我们的设计之初当然是使用人员自行决定需要多少物理机来分布5120个rocksdb实例。为什么不能布置在一台物理机上呢?原因如下:
1,过多的rocksdb 实例同时compaction的概率变高,对磁盘的压力过大。
2, 每个rocksdb会占用一定数量的文件描述符和内存,这个数字乘以5120很容易将系统资源耗尽。
总之,一定是更多的硬件资源提供更多的性能,建议新接触pika的同学可以用我们的经典模式,如果完全满足不了目前的需求,可以考虑用更多的物理资源,使用集群模式。
具体的性能测试可以参考 [3.2.x Performance](https://github.com/Qihoo360/pika/wiki/3.2.x-Performance)
#### 1. 所需要版本
Pika 从3.2.0版本之后支持sharding mode,建议用最新release。
#### 2. 基本操作
关于slots的基本操作详见 [slot commands](https://github.com/Qihoo360/pika/wiki/Pika分片命令)
#### 3. 配置文件说明
相关的配置文件调参
```
# default slot number each table in sharding mode
default-slot-num : 1024
# if this option is set to 'classic', that means pika support multiple DB, in
# this mode, option databases enable
# if this option is set to 'sharding', that means pika support multiple Table, you
# can specify slot num for each table, in this mode, option default-slot-num enable
# Pika instance mode [classic | sharding]
instance-mode : sharding
# Pika write-buffer-size
write-buffer-size : 67108864
# If the total size of all live memtables of all the DBs exceeds
# the limit, a flush will be triggered in the next DB to which the next write
# is issued.
max-write-buffer-size : 10737418240
# maximum value of Rocksdb cached open file descriptors
max-cache-files : 100
```
配置文件的说明可以在[配置说明](https://github.com/Qihoo360/pika/wiki/pika-配置文件说明)中找到。
特别说明的是write-buffer-size 代表的是每一个rockdb实例的每一个memtable的大小,所有的rocksdb的所有的memtable大小上限由
max-write-buffer-size控制。如果达到max-write-buffer-size数值,每个rocksdb 实例都会尝试flush当前的memtable以减少内存使用。
#### 4. 兼容codis,twemproxy方案
目前的分布式框架依赖于开源项目,目前pika兼容codis,twemproxy。
具体的兼容方案详见[Support Cluster Slots](https://github.com/Qihoo360/pika/wiki/Support-Cluster-Slots)
\ No newline at end of file
# 如何升级到Pika3.0
## 升级准备工作:
pika在2.3.3版本时为了确保同步的可靠性,增加了server-id验证功能,因此pika2.3.3~pika2.3.6与pika2.3.3之前的版本无法互相同步
* 如果你的pika版本<2.3.3, 你需要准备pika2.3.6及pika3.0.16的bin文件,这里需要注意的是3.0.x需要准备3.0.16以后的版本(或者3.0.6版本),其他版本pika不再能与低版本(2.3.X)进行同步,因此在升级时建议使用最新的3.0.x版本pika来完成整个操作。下文以3.0.16为例。
* 如果你的pika版本>=2.3.3, 你需要准备pika3.0.16的bin文件
* 如果你的pika版本>=2.3.3那么请从下列步骤5开始, 否则请从步骤1开始
* 如果你的pika非集群模式(单点), 且无法停机升级, 请在操作前为该pika补充一个从库
## 升级步骤:
1. 为pika 从库的配置文件增加masterauth参数, 注意, 该参数的值需要与主库的requirepass参数配置相同, 否则会造成验证失败
2. 使用新的(建议使用2.3.6)pika bin目录覆盖低版本目录
3. 分别关闭pika主,从库并使用新的pika bin文件启动
4. 登录从库恢复主从关系(若你的从库配置文件中已经配置了slaveof参数, 那么可忽略该步骤), 并观察同步状态是否为up
5. 将3.0.16版本的pika部署到本服务器
6. 登录从库执行bgsave, 此时你可以在从库的dump目录中获取一份全新的备份, 确保其中的info文件在之后的步骤中不丢失, 例如你可以将其中的信息复制一份
7. 使用pika3.0.16 tools目录中的nemo_to_blackwidow工具将读取该备份中的数据并生成与pika3.0.16新引擎匹配的数据文件, 该工具的使用方法为:
```
./nemo_to_blackwidow nemo_db_path(需要读取的备份文件目录配置) blackwidow_db_path(需要生成的新数据文件目录配置) -n(线程数量, 请根据服务器性能酌情配置, 避免消耗过多硬件资源)
例子: 低版本pika目录为pika-demo, 版本为3.0.16的pika目录为pika-new30:
./nemo_to_blackwidow /data/pika-demo/dump/backup-20180730 /data/pika-new30/new_db -n 6
```
8. 更新配置文件, 将转换好的目录(/data/pika-new30/new_db)配置为新pika的启动目录(配置文件中的db-path参数), 同时将identify-binlog-type参数配置为old, 确保3.0.16能够解析低版本pika的同步数据, 如果配置文件中存在slaveof信息请注释, 其余配置不变
9. 关闭本机从库(/data/pika-demo), 使用pika3.0.16的bin文件启动新的pika(/data/pika-new30/new_db)
10. 登录pika3.0.16, 并与主库建立同步关系, 此时请打开之前保存的备份目录中的info文件, 该文件的第4,5行分别为pika备份时同步的位置信息, 需要将该信息加入slaveof命令进行增量同步(在执行命令之前要确保主库对应的binlog文件还存在)
```
例子: info文件中的信息如下并假设主库ip为192.168.1.1端口为6666:
3s
192.168.1.2
6666
300
17055479
那么slaveof命令应为:
slaveof 192.168.1.1 6666 300 17055479
```
11. 观察同步状态是否为up, 确认同步正常后等待从库同步无延迟时该从库彻底升级完成, 此时可开始切主操作:
```
a.关闭从库的salve-read-only参数确保从库可写
b.程序端修改连接ip为从库地址, 将程序请求全部转向从库
c.在从库断开(slaveof no one)与主库的同步, 完成整个切主操作
```
12. 通过config set将identify-binlog-type配置为new并修改配置文件中该参数的值为new, 如果config set报错那么意味着你忽略了上个步骤中的c步骤
13. 此时整个升级已完成, 你获得了一个单点的pika3.0.16实例, 若需补充从库, 你可新建一个新的空的pika3.0.16或更新版本的pika实例并通同slaveof ip:port force命令来非常简单的实现主从集群的创建, 该命令能够完整同步数据后自动转换为增量同步
## 注意事项:
* 由于Pika3.0的引擎在数据存储格式上做了重新的设计,新引擎比老引擎更加节省空间,所以升级完毕后,发现Pika3.0的db要比原来小这是正常的现象
* 在数据量比较大的情况下,使用nemo_to_blackwidow工具将Nemo格式的db转换成blackwidow格式的db可能花费的时间比较久,在转换的过程中可能主库当时dump时对应的位置的binlog会被清除掉,导致最后无法进行增量同步,所以在升级之前要将原先的主库的binlog生命周期设置得长一些(修改expire-logs-days和expire-logs-nums配置项)
* 由于我们在pika3.0的版本对binlog格式做了修改,为了兼容老的版本,我们提供了identify-binlog-type选项,这个选项只有pika身份为从库的时候生效,当identify-binlog-type选项值为new的时候,表示将主库发送过来的binlog按照新版本格式进行解析(pika3.0+), 当identify-binlog-type选项值为old的时候, 表示将主库发送过来的binlog按照老版本格式进行解析(pika2.3.3 ~ pika2.3.6)
* Pika3.0对Binlog格式进行了更改,新版本的Binlog在记录更多数据的同时更加节省磁盘空间,所以在将pika2.3.6数据迁移到pika3.0过程当中判断主从同步是否无延迟时不要去对比主从各自的binlog_offset,而是看master上的Replication项slave对应的lag是否接近于0
# 如何升级到Pika3.1或3.2
# 迁移工具介绍
## manifest生成工具
* 工具路径`./tools/manifest_generator`
* 工具作用:用来进行manifest的生成
## 增量同步工具
* 工具路径`./tools/pika_port`
* 工具作用:用来进行pika3.0与新pika3.1或pika3.2之间的数据同步
# 说明
1. 为了提高pika的单机性能,自Pika3.1开始,我们在Pika中实现了redis的多库模式,正是因为如此底层存储db以及log的目录发生了一些变化,如果老版本pika的db路径是`/data/pika9221/db`,那么单db数据都会存在这个目录下面,但是由于我们目前支持了多db,目录层级比原先多了一层,所以迁移的时候我们需要手动将原先单db的数据挪到`/data/pika9221/db/db0`当中
2. 为了提高多DB的同步效率,在新版本Pika中我们使用PB协议进行实例间通信,这意味着新版本的Pika不能直接与老版本的Pika直接建立主从,所以我们需要[pika_port](https%3a%2f%2fgithub.com%2fQihoo360%2fpika%2fwiki%2fpika%e5%88%b0pika%e3%80%81redis%e8%bf%81%e7%a7%bb%e5%b7%a5%e5%85%b7)将老版本Pika的数据增量同步到新版本Pika上
# 升级步骤
1. 根据自己的场景配置新版本Pika的配置文件(databases项用于指定开启几个db)
2. 登录主库执行bgsave操作,然后将dump下来的数据拷贝到新版本pika配置文件中db-path目录的下一级目录db0中
```
例子:
旧版本Pika dump的路径为:/data/pika_old/dump/20190517/
新版本Pika db-path的路径为:/data/pika_new/db/
那么我们执行: cp -r /data/pika_old/dump/20190517/ /data/pika_new/db/db0/
```
3. 使用manifest_generator工具,在新版本pika配置log目录的下一级目录log_db0中生成manifest文件,这样可以让新的pika产生和老的pika一样的binlog偏移量,需要指定db-path/db0目录和$log-path/log_db0目录(相当于把老版的db和log合并到了新版的db0里面)
```
例子:
新版本Pika db-path的路径为: /data/pika_new/db/
新版本Pika log-path的路径为:/data/pika_new/log/
那么我们执行: ./manifest_generator -d /data/pika_new/db/db0 -l /data/pika_new/log/log_db0
```
4. 用v3.1.0版本的的二进制和对应的配置文件启动Pika,使用info log查看db0的binlog偏移量(filenum和offset)
5. 使用Pika-port工具将旧版Pika的数据增量同步到新版本Pika上面来
```
例子:
旧版本Pika的ip为:192.168.1.1,端口为:9221
新版本Pika的ip为:192.168.1.2,端口为:9222
执行pika_port工具机器的本地ip为:192.168.1.3, 打算使用的端口为:9223
获取的filenum为:100,获取的offset为:999
那么我们执行:./pika_port -t 192.168.1.3 -p 9223 -i 192.168.1.1 -o 9221 -m 192.168.1.2 -n 9222 -f 100 -s 999 -e
```
6. 在使用pika-port进行同步的过程中我们可以登录主库执行`info replication`打印出的数据里会发现新增了一条从库信息,他是pika_port工具模仿slave行为与源库进行交互,同时可以通过lag查看他的延迟
7. 当我们与pika3.1或pika3.2进行增量同步的时候,可以对pika3.1或pika3.2进行从库添加操作,这样的话在从库都同步完成之后,我们在源库上查看lag如果为0 或者是很小的时候我们可以将整个集群进行替换,把原来的集群替换掉,把新集群上线
# 注意事项
1. 当我们在拷贝dump目录的时候,最好先mv改名字,然后在进行远程同步,这可以防止dump目录在拷贝的时候覆盖而造成数据不一致的结果
2. 在我们使用manifest_generator工具的时候,他需要dump时候生成的info文件,所以在拷贝dump目录的时候,要保证info文件也移动到指定的目录底下
3. 使用manifest_generator的时候 $log-path/db0 目录如果存在是会报错的,所以不要新建db0目录,脚本会自动创建
4. pika_port增量同步工具需要依赖info文件里的ip port 和file offset进行同步。
5. pika_port会模仿slave与源库交互,所以他会进行trysync,当他请求的点位在源库过期的时候,就会触发全同步,会自动的使用-r 这个参数记录的目录来存放rsync全量同步的数据,如果不想让他自动的进行全同步,可以使用-e参数,当进行全同步的时候会返回-1,但是这时候rsync还是再继续,需要kill 本地的rsync进程,源库才会终止全量同步
6. pika_port进行增量同步是持续性的,不会断的,这个时候可以在源库上使用 info replication 查看slave的lag来确定延迟
7. pika_port工具支持多线程应用,根据key hash,如果是同一个key会hash到一个线程上去回放,可以保证同一个key的顺序
8. 在数据量比较大的情况下,使用拷贝dump目录可能花费的时间比较久,导致主库当时dump时对应的位置的binlog会被清除掉(或者pika3.1(pika3.2)新增从库的时候也需要注意),导致最后无法进行增量同步,所以在升级之前要将原先的主库的binlog生命周期设置得长一些(修改expire-logs-days和expire-logs-nums配置项)
9. 如果我们不用manifest_generator 生成manifest文件也是可以的,但是这个时候启动的pika3.1或pika3.2实例的点位是 0 0 ,如果后续在pika3.1或pika3.2后挂载从库的话,需要在从库上执行`slaveof IP PORT force`命令,否则的话从库可能会出现少数据的情况
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册