携程持久化KV存储挑战Redis,狂省90%成本( 三 )


携程持久化KV存储挑战Redis,狂省90%成本
文章图片

文章图片
图4
这样就很容易发生同机房的slave数据复制的进度要快于异地机房的slave 。如果发生机房级的故障 , 导致master所在集群的服务全都无法正常工作 , 这个时候就可能发生数据丢失 。
为此我们在半同步复制的基础上增加了IDC模式 , 使得即使初始条件已经满足 , 也需要至少存在相关IDC的slave反馈才能完成整个复制流程 。IDC模式有两种 , 本地复制和异地复制 。
以异地模式为例 , 如果返回slave的数量满足条件 , 且包含至少一台来自于master所在机房不同的slave , 则半同步复制完成 。如果当前响应中未包含非master集群的slave , 则继续等待 , 直到master接收到一台来自异地的slave的反馈 , 半同步才能完成(图5) 。
携程持久化KV存储挑战Redis,狂省90%成本
文章图片

文章图片
图5
尽管异地模式数据的安全性更高 , 但也会影响整个系统的性能 , 这个性能差正常情况下取决于不同机房之间的网络延迟 。基于对性能和数据可用性的不同要求 , 使用方可以酌情选择全异步复制(即关闭半同步) , 半同步&半同步(本地)复制或者半同步(异地)复制 。全量同步复制抑制
上面说到异步复制在异常情况下可能存在数据缺失的情况 , 如果再加上运维系统对主从关系的调整 , 就会发生数据冲突 。而我们目前TRocks的版本还在快速迭代中 , 希望每次升级版本能够对用户透明 , 然而事实并非如此 。
假设存在masterA和slaveB , 正常情况下A和B的数据是保持一致的(绿色部分) , 但当A发生宕机的时候 , B可能还未同步到A的最新数据 , 这时B的数据不再增加 。但随后哨兵发现master无法访问 , 就把B提升为master并开始处理写入数据(蓝色部分) 。当一段时间后 , A系统恢复 , 重新加入进集群 , 此时A会变为masterB的slave , 并尝试从B中同步数据 , 这里就可能存在冲突区(图6) 。
携程持久化KV存储挑战Redis,狂省90%成本
文章图片

文章图片
图6
按照Kvrocks初始的复制逻辑 , A会认为自身数据存在问题 , 并放弃全部数据然后从头开始进行全量同步B的数据 。这个行为本身没有问题 。然而实际生产环境下 , 如果数据量很大的话 , 全量同步的耗时会比较长 , 而硬盘相比内存的带宽至少小两个数量级 , 因为我们的实例都是容器化部署 , 这有可能导致灾难性的后果 , A在同步数据的时候会产生大量的IO , 从而可能会影响A/B所在的宿主机上的所有的实例 。
在数据一致性要求没有那么高的场景中 , 仅仅因为可能的几条数据不一致就重新同全量同步 , 代价非常昂贵 。所以我们希望在非强一致性条件下 , 系统可以容忍极少量的数据差异 , 尽可能避免全量同步以便充分利用资源 。
我们的方案是当检测到数据不一致的时候 , 主从之间会进行交互协调 , 计算出冲突区的范围 , 并从冲突区之后第一条数据开始进行同步 。为什么不是直接从冲突区后面开始同步?这里需要有个概念 , TRocks/Kvrocks的数据都是追加形式的 , 增删改都会在log文件中追加一条记录 , 并提供起始位置(Sequence) , 对应不同的Redis类型的记录会有不同的长度(Count) , 比如一条SET指令对应的Sequence会累加1 , 而HSET指令会累加2 。
从Sequence到Sequence+Count就是一条记录的数据范围 。当重新同步的时候 , 冲突区的结束位置如果处于正常数据的中间 , 这样是没有办法取得完整数据的 , 所以需要从冲突区后第一条数据开始 。