pika学习之同步篇

  • pika支持m-s的复制方式,跟redis主从复制命令一样,通过slave执行slaveof命令来触发。
  • slave的trysync线程向master发起trysync,同时将同步位点信息发送给master
  • master处理trysync命令,发起对slave的同步过程,从同步点开始顺序发送binlog或进行全同步

全量同步

pika在需要进行全量同步的时候,会将数据文件进行dump后通过rsync的deamon模式发送给slave。

实现逻辑

  1. slave在trysnc前启动rsync进程启动rsync服务
  2. master发现需要全同步时,判断是否有备份文件可用,如果没有先dump一份
  3. master通过rsync向slave发送dump出的文件
  4. slave用收到的文件替换自己的db
  5. slave用最新的偏移量再次发起trysnc
  6. 完成同步

slave同步流程

master同步流程

slave连接状态:

  • No Connect:不尝试成为任何其他节点的slave
  • Connect:Slaveof后尝试成为某个节点的slave,发送trysnc命令和同步点
  • Connecting:收到master回复可以slaveof,尝试跟master建立心跳
  • Connected: 心跳建立成功
  • WaitSync:不断检测是否DBSync完成,完成后更新DB并发起新的slaveof

增量同步

Pika的主从同步是通过Binlog来完成的,在一主多从的结构中,master节点也可以给多个slave复用一个Binlog,不同的slave拥有不同的偏移量。

工作过程

  1. 当WorkerThread接收到客户端的命令,按照执行顺序,添加到Binlog里
  2. BinglogSenderThread判断它所负责的从节点在主节点的Binlog里是否有需要同步的命令,若有则发送给从节点
  3. BinglogReceiverModule模块则做以下三件事情:接收主节点的BinlogSenderThread发送过来的同步命令;把接收到的命令应用到本地的数据上;把接收到的命令添加到本地Binlog里 至此,一条命令从主节点到从节点的同步过程完成
  • WorkerThread:接受和处理用户的命令
  • BinlogSenderThread:负责顺序地向对应的从节点发送在需要同步的命令
  • BinlogReceiverModule: 负责接受主节点发送过来的同步命令
  • Binglog:用于顺序的记录需要同步的命令

上图中的BinLogReceiverModule是为了更好的说明方便而抽象出来的一个对象,从图中可以看出BinLogReceiverModule是由一个BinlogReceiverThread和多个BinlogBGWorker组成。

  • BinlogReceiverThread: 负责接受由主节点传送过来的命令,并分发给各个BinlogBGWorker,若当前的节点是只读状态(不能接受客户端的同步命令),则在这个阶段写Binlog
  • BinlogBGWorker:负责执行同步命令;若该节点不是只读状态(还能接受客户端的同步命令),则在这个阶段写Binlog(在命令执行之前写)

BinlogReceiverThread接收到一个同步命令后,它会给这个命令赋予一个唯一的序列号(这个序列号是递增的),并把它分发给一个BinlogBGWorker;而各个BinlogBGWorker则会根据各个命令的所对应的序列号的顺序来执行各个命令,这样也就保证了命令执行的顺序和主节点执行的顺序一致了 之所以这么设计主要原因是:

  1. 配备多个BinlogBGWorker是可以提高主从同步的效率,减少主从同步的滞后延迟
  2. BinlogBGWorker在执行执行之前写Binlog可以提高命令执行的并行度
  3. 在当前节点是非只读状态,让BinglogReceiverThread来写Binlog,是为了让Binglog里保存的命令顺序和命令的执行顺序保持一致

Binlog结构

由于主从偏移量一样,所以一旦发生网络或节点故障需要重连主从时,slave只需要将当前的偏移量发送给master,master找到后从该偏移量开始同步后续命令。
理论上命令不做处理一条一条追加到Binlog文件中,这也就意味着如果文件写错一个字节就会导致整个文件不可用,所以pika采用了类似leveldb log的格式来进行存储。

leveldb log

在leveldb中,所有的写操作都必须先成功的append到操作日志中,然后再更新内存memtable

  1. 可以将随机的写IO变成append,极大的提高写磁盘速度
  2. 防止在节点down机导致内存数据丢失,造成数据丢失
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    The log file contents are a sequence of 32KB blocks.  The only
    exception is that the tail of the file may contain a partial block.

    Each block consists of a sequence of records:
    block := record* trailer?
    record :=
    checksum: uint32 // crc32c of type and data[] ; little-endian
    length: uint16 // little-endian
    type: uint8 // One of FULL, FIRST, MIDDLE, LAST
    data: uint8[length]

    A record never starts within the last six bytes of a block (since it
    won't fit). Any leftover bytes here form the trailer, which must
    consist entirely of zero bytes and must be skipped by readers.

日志文件由连续的大小为32KB的block组成,block又由连续的record组成,record的格式为 | CRC(4 byte) | Length(2 byte) | type(1 byte) | data |

LevelDB 功能与架构