redis异地多活
what:
异地多活:
简单来说,就是在不同地域建立数据中心,每个数据中心在日常使用中都需要正常接入业务流量,做业务支撑。
异地多活,也属于分布式架构的系统。也绕不开CAP(参考:CAP详解)
异地多活优势:
1、可用性更强:(如下图)地域 1 机房故障,甚至地域 2 机房也故障,只要地域 3 的机房是可用的,流量都可以切到地域 3 机房,线上的业务也不会中断。
2、性能提升:多个数据中心,可供客户端就近访问,那么性能可能提升几个量级。例如:从北京到上海共 1468 公里,即使是光速传输,一个来回也需要接近 10ms;实际测试中,延迟大概在 30ms 左右。
why:
原生Redis做异地多活面临多种问题:
1、断点续传:2.8后有续传能力,并且4.0后replid 与 offset 持久化在 rdb 中,重启/主备切换后可以续传。只是“减小了全量重传的概率”。
2、双活时无法进行全量同步:原生只有master-slave模式。
3、无法去环:(如下图)当同步程序从地域 1 集群向地域 2 集群同步set key val
命令,地域 2 集群写入同步程序发送过来的命令,接着地域 2 的同步程序又向地域 3 的集群发送当前变更命令;同理,地域 3 的同步程序又向地域 1 的集群同步,以此类推……
4、 可扩展性差,实现多活架构复杂。可以借助组件,例如:客户端将命令变更发送到 kafka,再由同步程序将变更从 kafka 消费然后写入目标集群,从而实现数据同步。
how:
考虑到 redis 大部分作为纯缓存的应用场景,在设计异地多活时,为了保证缓存的高可用性,防止因校验数据一致带来的性能损耗,甚至集群不可用导致缓存击穿,所以我们将 redis 异地版本,设计为优先遵循 AP 原则,即:一个地域集群实例故障再恢复时,同一时段内会适当容忍部分数据不一致的情况,但保持最终数据一致性。
回环问题:
在RESPv2 协议前增加 3 个标志位:
server-id
: 对应 redis 实例全局唯一的 id; opid
: redis 变更操作编号; Src-id
: 源 redis 实例 id,用于区分哪些数据是接收同步过来的,哪些是自己的;
例如:serverA 写入了一条编号为 101 的变更操作,随后收到同步程序发送的 serverB 的相关变更数据;收到 serverB 发送的数据后,serverA经过一系列校验,写入 AOF 文件,将 src-id和opid 置为serverB对应的值;同步程序发送增量数据时,serverA直接略过 src_id 不为 A 且 opid 不为变更的数据即可。
解决多活一致性问题 : (参考CRDT详解)
多数据中心,需要保持数据的一致性,至少保证数据的最终一致性。而根据CAP的理论,在保证AP的同时,强的C就很难保证,以至于有了最终一致性的方案CRDT。
CRDT(Conflict-Free Replicated Data Type)是各种基础数据结构最终一致算法的理论总结,能根据一定的规则自动合并,解决冲突,达到强最终一致的效果。CRDT保证最终一致的3个理论依据是:
结合律:(X U Y) U Z = X U (Y U Z)
交换律:X U Y = Y U X
幂等律:X U X = X
redis的具体方案:
Counter 类 - Redis 对应的命令操作(incr,decr,incrby,decrby)。decr可以理解为incr一个负数,那么就符合“交换和结合”律,幂等不行,则需要保证写操作不能丢。
Register 类 - Redis 对应的命令操作(set)。(register本质是一个string,仅支持一种写操作assign。并发assign是不存在交换律的,所以需要考虑附加上偏序关系)。set 不满足交换类,所以改造时需要增加一些附加的元信息,来保证更新的单调性。这里采用 LWW Register,既 Last-Write-Win,通过附加时间戳信息,来保证所有异地集群,都是最后写入的最新数据。
Set 类 - Redis 对应命令操作(集合类命令、Hash 命令)。Set一共有两种写操作,add和remove,多节点并发进行add和remove操作是无法满足交换律的。会如下图出现不一致。一般的解决方案为“LWW-element-Set、Observed-Remove Set (OR-Set)”