redis单元化
背景
单元化:指异地多活,比如两个相离很远的大区域,如北京与深圳两个副本,是两份相同的数据,业务上层保证指对自己本地的数据做写操作,可以读其他地数据。
单元化主要有两个优势
- 异地多活
- 读写本地单元时,时延低。按常规的主从复制做的话,读写一个异地的idc时延会很高。
本文不详细讨论单元化的优势、理念,主要讲解一种redis单元化的实现思路。
方案总体如下
- 通过一个同步工具将两个redis主节点构成一条单元化链接,该同步工具可以单向或双向的同步两者的写操作。
- 将对端单元传过来的数据加上单元信息(如单元归属),本单元不允许对其他单元的key做任何的写操作(比如淘汰、过期本单元均不操作)。
同步工具行为
下文称单元1的redis-server为r1,同理单元2的为r2。以r1为源,r2为目标的情况下讲解。
理念
首先redis引入单元化同步的进度结构,相当于在常规的repl_offset增加了一个单元化的unit_repl_offset。
同步工具在配置文件中配置好两个单元的同步节点,规定好一条同步链路,比如源节点和目标节点的信息。
同步工具有一个设计理念: 一条命令永远不会经过同步工具两次以防止命令回环。
其通过以下方法实现
- 每一个真实命令通过其前,会先追加一条自定义的
unit_xx
命令,该命令被规定为写命令 - 因此回环时,会先收到
UNIT_SYNC_BINLOG
命令,那么意味着后续跟着的真实命令是回环命令,可以直接丢弃不予通过。
这里说一个重要思想,针对同步缓存区:
- 每个节点记录好自身的单元化的repl_id和offset(新数据结构),主要用于重连时让同步工具能获取到,该节点单元化的同步位点。
- 对于一个单一节点而言,单元化写操作的binlog和一般的命令产生的binlog是共用缓冲区的,同样共用repl_id和offset(redis原生结构)。所以当收到一个psync repl_id offset命令时(无论是否为单元化),实际上都是将backup_log中的该offset之后的数据发给对端。
开始同步
- 同步工具向r2发起连接,通过自定义命令
UNIT_SYNC_NEW_SESSION
,参数为源单元名与同步链路名,以及此次session的id字符串。 - 可以从r2收包拿到其单元化的repl_id和offset,后续用于向r1发起同步用
- 建立与r1的连接,发起
unit_psync
命令附带刚获取的repl_id
和offset
。这里可以将其理解为psync2命令。 - r1收到该同步请求时,判断backup_log是否还存在该offset(和redis常规情况一致),其走增量返回
CONTINUE
还是全量返回FULLRESYNC
,以全量举例,r1顺带返回了new_repl_id
和new_offset
- 同步工具开始获取和解析rdb。这里详细阐述一下
每一个rdb的key-value对,会把他做成一个multi exec
命令,一共附带3条命令
UNIT_SYNC_BINLOG
,附带session_id、binlog_seq(恒递增)、repl_id、offset、源单元名和同步链路名。注意在全量同步解析rdb时,repl_id和offset可以置为初始值,因为不关心。UNIT_SYNC_PRE_RESTORE
+key
,告知对端即将RESTORE
RESTORE
+ 命令参数REPLACE
和ABSTTL
。
同步工具会将所有拿到的binlog都存在本地文件里,即内存中不会放无限递增的binlog请求,到了一定程度便持久化到本地文件中。
- 最终在rdb所有命令解析为binlog后增加一条
UNIT_SYNC_BINLOG
,附带真正的repl_id
和repl_offset
,用于告知对端其同步位点。
同步稳态
稳态即同步工具和r1已经开始稳定的增量同步,同步工具收到包后将其持久化到本地文件里,并定期回复REPLCONF ACK offset
。
同步工具持续的从本地文件中拉取multi命令包装的binlog发送给r2,相当于自己就是一个业务写入方。通过r2的回复,来裁剪本地文件的binlog数据。
跟R1的稳态交互为:
- 同步工具持续解析从r1收命令,如果是
unit
命令,只更新offset,不持久化到文件。否则包装成MULTI
命令附带unit
命令,持久化到文件中。 - 每一秒回复r1一次
REPLCONF ACK offset
。
跟R2的稳态交互为:
- 持续的从文件中取出之前持久化的
cmds
,如果db文件里没有,则检查binlog_buf中有没有,将这些发送给r2,将已经发送的未收到回复的请求f附带seq记录在队列binlog_info_q
里。 - 持续的从r2收回复包,每收到一个命令的回复,删除
binlog_info_q
这里面的这条记录,推进队列的总进度。 - 每一段时间根据
binlog_info_q
记录的进度,删除db文件中的对方已经收到的命令。
redis-server行为
自定义命令与处理
UNIT_SYNC_NEW_SESSION
1 | cmd = "KUNIT_SYNC_NEW_SESSION", |
这个命令主要是同步工具启动初始化时,获取r2的过往同步进度的。比如tcp重连。通过这个命令可以拿到当前同步链路的repl_id和offset。
UNIT_SYNC_BINLOG
1 | cmd = "KUNIT_SYNC_BINLOG", |
通过这个命令更新r2的session中的offset。并设置好接下来的命令的key的单元归属信息,以及该命令表明为单元化同步的命令。
UNIT_SYNC_PRE_RESTORE
1 | cmd = "KUNIT_SYNC_PRE_RESTORE", |
这条命令用于在restore key之前做校验,必须处于上面binlog命令的事务中。比如一次全量同步时,r1发过来的key的单元归属信息会指定为r1,但实际上有可能是r2的数据。
那么r2可以通过这个命令检查key的单元归属信息拦截掉相应的写请求。
实际写命令处理
对于一般的写命令,我们将单元归属信息都置为unit_id=0
,表明该key属于本单元。
而单元化的写命令,都是在MULTI
中,在EXEC
执行前,清空当前所有的预置的单元归属信息。然后依次执行,在执行到UNIT_SYNC_BINLOG
时,设置到接下来的key的单元归属信息。
之后在dbAdd时,把这个object-val的单元归属信息加上即可。