广州网站建设出名 乐云践新,南宁网站托管,北京网站制作工作室,服务器如何发布网站为了更好的做好容灾保障#xff0c;使业务能够应对机房级别的故障#xff0c;滴滴的存储服务都在多机房进行部署。本文简要分析了 Redis 实现异地多活的几种思路#xff0c;以及滴滴 Redis 异地多活架构演进过程中遇到的主要问题和解决方法#xff0c;抛砖引玉#xff0c;… 为了更好的做好容灾保障使业务能够应对机房级别的故障滴滴的存储服务都在多机房进行部署。本文简要分析了 Redis 实现异地多活的几种思路以及滴滴 Redis 异地多活架构演进过程中遇到的主要问题和解决方法抛砖引玉给小伙伴们一些参考。 Redis 异地多活的主要思路 业界实现 Redis 异地多活通常三种思路主从架构、Proxy双写架构、数据层双向同步架构。 主从架构 主从架构的思路 各机房的 Redis 通过 Proxy 对外提供读写服务业务流量读写本机房的 Redis-proxy主机房里的 Redis-master 实例承担所有机房的写流量从机房里的 Redis-slave 实例只读承担本机房里的读流量 主从架构的优点 实现简单在 Proxy 层开发读写分流功能就可以实现Redis 层使用原生主从复制可以保证数据一致性 主从架构的缺点 从机房里的 Redis-proxy 需要跨机房写受网络延时影响业务在从机房里的写耗时高于主机房主机房故障时从机房的写流量也会失败需要把从机房切换为主机房切换 Redis-master网络故障时从机房的写流量会全部失败为了保障数据一致性这种场景比较难处理 Proxy 双写架构 Proxy 双写架构的思路 各机房的 Redis 通过 Proxy 对外提供读写服务业务流量读写本机房的 Redis-proxy不区分主从机房每个机房都是独立的 Redis 集群各机房的读写流量都是访问本机房的 Redis 集群Proxy 层在写本机房成功后将写请求异步发送到对端机房 Proxy 双写架构的优点 实现简单在 Proxy 层开发双写功能就可以实现一个机房故障时其他机房的流量不受影响网络故障时各机房内部的流量也不受影响 Proxy 双写架构的缺点 不能保证数据一致性Proxy 异步 write 请求可能会失败失败丢弃请求后导致双机房数据不一致假设机房-A的集群先上线机房-B 后上线Proxy 双写架构不能支持把机房-A的存量数据同步到机房-B网络故障时异步 write 会失败后丢弃网络恢复后之前失败的数据已经丢弃导致双机房数据不一致 数据层双向同步架构 数据层双向同步架构的思路 Proxy 不关心底层 Redis 数据同步业务流量只访问本机房里的 Redis 集群在 RedisServer 层面实现数据同步 数据层双向同步架构的优点 机房-A故障时机房-B不受影响反向如是网络故障时本机房流量不受影响网络恢复后数据层面可以拉取增量数据继续同步数据不丢支持存量数据的同步业务访问 Redis 延时低访问链路不受机房间网络延时影响业务单元化部署时双机房 Redis 会有较高的数据一致性 数据层双向同步架构的缺点 实现相对比较复杂RedisServer 改动比较大 滴滴 Redis 架构 Codis 架构早期架构现已废弃 Kedis 架构线上架构 滴滴 Redis 异地多活架构的演进 第一代多活架构 第一代 Redis 多活基于 Codis 架构在 proxy 层实现了双写即本机房的 Proxy 将写流量转发到对端机房的 Proxy这个方案的特点是快速实现尽快满足了业务多机房同步的需求。如前面 Proxy 双向架构思路所讲本方案还存在着诸多缺点最主要的是网络故障时同步数据丢失的问题为了解决这些问题我们开发了第二代多活架构。 第二代多活架构 第二代多活基于 Kedis 架构对 Redis-server 进行改造可以把增量数据从 Redis 直接写入本机房的 MQ 中由对端机房的 consumer 来消费 MQconsumer 将数据写入对端 Redis 中。网络故障时数据会在 MQ 堆积待网络恢复后consumer 可以基于故障前的 offset 继续进行消费写入对端 Redis从而保证在网络故障时 Redis 多活不会丢数据。 但这一代架构仍不够完美存在以下问题 ProducerThread 把数据写入 MQ 时如果触发 MQ 限流数据会被丢掉RedisServer 内部包含了 ProducerThread当中间内部 queue 累积数据量超过10000条时数据会被 MainThread 丢掉中间同步数据写入 MQ增加了跨部门依赖同步链路长不利于系统稳定性中间同步链路重试会造成非幂等命令执行多次例如 incrby 重试可能造成命令执行多次造成数据不一致对于新建双活链路不支持同步存量数据只能从当前增量数据开始同步Redis 增量数据写入 MQ导致成本增加 为了解决以上问题我们开发了第三代架构。 第三代多活架构 在第三代架构中我们细化了设计目标主要思路是保证同步链路中的数据不丢不重同时去掉对 MQ 的依赖降低多活成本。 第三代架构中我们去掉了 MQ 和 consumer新增了 syncer 组件。syncer 组件模拟 Redis-slave 从 Redis-master 中拉取增量数据这样把数据同步和 Redis 进行解耦便于后续多机房扩展。 在第三代架构中Redis 遇到了回环、重试、数据冲突、增量数据存储和读取等问题接下来一一介绍我们应对这些问题的解决方案。 1、回环问题 机房-A 写入的数据同步到机房-B防止数据再传回机房-A。 为了解决回环问题我们开发了防回环机制 Redis 增加 shardID 配置标识唯一分片号Redis 请求中增加 opinfo记录元信息包含 shardID 机房-A 的 Proxy 写入了 set k v 请求机房-A 的 Redis-master 向 syncer 同步 set k v opinfo[shardID-1] 请求syncer 向机房-B 写入 set k v opinfo[shardID-1] 请求这样机房-B 根据 shardID-1 识别出这条请求是机房-A 生产的数据因此不会再向机房-A 同步本条请求 2、重试问题 机房-A 写入的 incrby 请求同步到机房-B由于中间链路的重试导致机房-B 可能执行了多次。 为了解决重试问题我们开发了防重放机制 Redis 增加 opid标识唯一请求号Redis 请求中增加 opinfo记录元信息[opid] 机房-A 的 Proxy 写入了 incrby k 1 请求机房-A 的 Redis-master 向 syncer 同步了 incrby k 1 opinfo[opid100] 请求 之前同步的 opid99 的请求已经成功syncer 向机房-B 写入 incrby k 1 opinfo[opid100] 请求机房-B 的 Redis 里存储了防重放信息 shardID-1-opid[99]机房-B 的 Redis 发现新请求的 opid100本地的99判断为新请求机房-B的 Redis 执行这条请求并把防重放信息更新为shardID-1-opid[100]假设机房-A 的 syncer 将本条请求进行了重试又执行了一遍 incrby k 1 opinfo[opid100]机房-B 的 Redis 发现新请求 opid100 等于本地的100判断为重复请求机房-B 的 Redis 忽略掉本地请求不执行 3、数据冲突问题 双机房同时修改同一个 key 导致数据不一致 对于数据冲突不同数据类型的不同操作的数据合并如果单从存储层解决是一个非常复杂的话题。如果业务层做了单元化部署则不会出现这种问题。如果业务层没有做单元化我们开发了冲突检测功能来帮助业务及时发现数据冲突最后数据以哪边为准来修正需要业务同学来决策。 冲突检测机制 Redis 记录 key 的最后 write 时间Redis 请求中增加 opinfo记录元信息 [timestamp]如果 opinfo.timestampkey_write_time则记录冲突 key 时间T1T2T3 T1时间用户在机房-A 写入请求 set k v1T2时间用户在机房-B 写入请求 set k v2并记录k的最后修改时间为T2由于网络同步延时T3时间syncer 把T1时间写入的 set k v1请求发送到了机房-B机房-B 的 Redis 执行 set k v1 时发现 timestamp 为T1但 k 的最后修改时间为T2由于T1T2机房-B 的 Redis 判断这是一次冲突并记录下来然后执行该条请求 以上是冲突检测的基本原理这是一个旁路统计帮助用户发现一些潜在冲突数据。 4、增量数据存储和读取问题 因为 syncer 只是同步组件不会存储数据所以需要考虑当网络故障时增量数据的存储和读取问题。 为了解决这个问题我们对 Redis 的 aof 机制进行了改造可以在网络故障时增量数据都堆积在 Redis 的磁盘上在网络恢复后syncer 从 Redis 里拉取增量 aof 数据发送到对端机房避免数据丢失。 aof 机制改造有aof 文件切分、aof 增量复制、aof 异步写盘 将 aof 文件切分为多个小文件保存增量数据当增量数据超过配置的阈值时Redis 自动删除最旧的 aof 文件当 Redis 重启时加载 rdb 文件和 rdb 之后的 aof 文件可以恢复全部数据当网络故障恢复后syncer 根据故障前的 opid 向 Redis 请求拉取增量数据发送到对端机房 开源 Redis 是在主线程中进行 aof 写盘当磁盘 IO 过高时Redis 写盘可能造成业务访问 Redis 耗时抖动。因此我们开发了 aof 异步写盘机制 Redis 的主线程将 aof 数据写入 queue 中bio 线程来消费 queuebio 线程将 aof 数据写入磁盘 这样 Redis 的访问耗时不受磁盘 IO 的影响更好的保证稳定性。