Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 理解了复制原理之后,本文我们重点分析基于复制的应用场景。通过复制机制,数据集可以存在多个副本(从节点)。这些副本可以应用于读写分离、故障转移(failover)、实时备份等场景。但是在实际应用复制功能 时,依然有一些坑需要跳过

一、读写分离

  • 对于读占比较高的场景,可以通过把一部分读流量分摊到从节点来减轻主节点压力,同时需要注意永远只对主节点执行写操作。如下图所示:

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 当使用从节点响应读请求时,业务端可能会遇到如下问题:
    • 复制数据延迟
    • 读到过期数据
    • 从节点故障

①数据延迟

  • Redis复制数据的延迟由于异步复制特性是无法避免的,延迟取决于网络带宽和命令阻塞情况,比如刚在主节点写入数据后立刻在从节点上读取可能获取不到。需要业务场景允许短时间内的数据延迟
  • 对于无法容忍大量延迟场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点。实现逻辑如下图所示:
    • 1)监控程序(monitor)定期检查主从节点的偏移量,主节点偏移量在info replication的master_repl_offset指标记录,从节点偏移量可以查询主节点的slave0字段的offset指标,它们的差值就是主从节点延迟的字节量
    • 2)当延迟字节量过高时,比如超过10MB。监控程序触发报警并通知客户端从节点延迟过高。可以采用Zookeeper的监听回调机制实现客户端通知
    • 3)客户端接到具体的从节点高延迟通知后,修改读命令路由到其他从节点或主节点上。当延迟恢复后,再次通知客户端,恢复从节点的读命令请求

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 这种方案的成本比较高,需要单独修改适配Redis的客户端类库。如果涉及多种语言成本将会扩大。客户端逻辑需要识别出读写请求并自动路由, 还需要维护故障和恢复的通知。采用此方案视具体的业务而定,如果允许不 一致性或对延迟不敏感的业务可以忽略,也可以采用Redis集群方案做水平扩展

②读到过期数据

  • 当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略。删除策略主要有两种:惰性删除和定时删除,具体细节见后面文章“内存管理”
  • 惰性删除:主节点每次处理读取命令时,都会检查键是否超时,如果超时则执行del命令删除键对象,之后del命令也会异步发送给从节点。需要注意的是为了保证复制的一致性,从节点自身永远不会主动删除超时数据,如下图所示

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 定时删除:Redis主节点在内部定时任务会循环采样一定数量的键,当发现采样的键过期时执行del命令,之后再同步给从节点,如下图所示

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 如果此时数据大量超时,主节点采样速度跟不上过期速度且主节点没有读取过期键的操作,那么从节点将无法收到del命令。这时在从节点上可以读取到已经超时的数据。Redis在3.2版本解决了这个问题,从节点读取数据之前会检查键的过期时间来决定是否返回数据,可以升级到3.2版本来规避 这个问题

③从节点故障

  • 对于从节点的故障问题,需要在客户端维护可用从节点列表,当从节点故障时立刻切换到其他从节点或主节点上。这个过程类似上文提到的针对延迟过高的监控处理,需要开发人员改造客户端类库
  • 综上所出,使用Redis做读写分离存在一定的成本。Redis本身的性能非 常高,开发人员在使用额外的从节点提升读性能之前,尽量在主节点上做充 分优化,比如解决慢查询,持久化阻塞,合理应用数据结构等,当主节点优 化空间不大时再考虑扩展。笔者建议大家在做读写分离之前,可以考虑使用 Redis Cluster等分布式解决方案,这样不止扩展了读性能还可以扩展写性能和可支撑数据规模,并且一致性和故障转移也可以得到保证,对于客户端的 维护逻辑也相对容易

二、主从配置不一致

  • 主从配置不一致是一个容易忽视的问题
  • 对于有些配置主从之间是可以不一致,比如:主节点关闭AOF在从节点开启
  • 但对于内存相关的配置必须要一致,比如maxmemory,hash-max-ziplist-entries等参数:
    • 当配置的maxmemory从节点小于主节点,如果复制的数据量超过从节点maxmemory时,它会根据maxmemory-policy策略进行内存溢出控制,此时从节点数据已经丢失,但主从复制流程依然正常进行,复制偏移量也正常
    • 修复这类问题也只能手动进行全量复制。当压缩列表相关参数不一致时,虽然主从节点存储的数据一致但实际内存占用情况差异会比较大。更多压缩列表细节见后面的文章“内存管理”

三、规避全量复制

  • 全量复制是一个非常消耗资源的操作,前面做了具体说明。因此如何规避全量复制是需要重点关注的运维点
  • 下面我们对需要进行全量复制的场景逐个分析:

①第一次建立复制

  • 由于是第一次建立复制,从节点不包含任何主节点数据,因此必须进行全量复制才能完成数据同步。对于这种情况全量复制无法避免
  • 当对数据量较大且流量较高的主节点添加从节点时,建议在低峰时进行操作,或者尽量规避使用大数据量的Redis节点

②节点运行ID不匹配

  • 当主从复制关系建立后,从节点会保存主节点的运行ID,如果此时主节点因故障重启,那么它的运行ID会改变,从节点发现主节点运行ID不匹配时,会认为自己复制的是一个新的主节点从而进行全量 复制
  • 对于这种情况应该从架构上规避,比如提供故障转移功能。当主节点发生故障后,手动提升从节点为主节点或者采用支持自动故障转移的哨兵或 集群方案。

③复制积压缓冲区不足

  • 当主从节点网络中断后,从节点再次连上主节点时会发送psync {offset} {runId}命令请求部分复制,如果请求的偏移量不在主节点的积压缓冲区内,则无法提供给从节点数据,因此部分复制会退化为全量复制
  • 针对这种情况需要根据网络中断时长,写命令数据量分析出合理的积压缓冲区大小:
    • 网络中断一般有闪断、机房割接、网络分区等情况。这时网络中断的时长一般在分钟级(net_break_time)
    • 写命令数据量可以统计高峰期主节点每秒info replication的master_repl_offset差值获取(write_size_per_minute)。积压缓冲区默认为1MB,对于大流量场景显然不够,这时需要增大积压缓冲区,保证 repl_backlog_size>net_break_time*write_size_per_minute,从而避免因复制积压缓冲区不足造成的全量复制

四、规避复制风暴

  • 复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。复制风暴对发起复制的主节点或者机器造成大量开销,导致CPU、内存、带宽消耗。因此我们应该分析出复制风暴发生的场景,提前采用合理的方式规避。规避方式有如下几个:

①单主节点复制风暴

  • 单主节点复制风暴一般发生在主节点挂载多个从节点的场景
  • 当主节点重启恢复后,从节点会发起全量复制流程,这时主节点就会为从节点创建 RDB快照,如果在快照创建完毕之前,有多个从节点都尝试与主节点进行全 量同步,那么其他从节点将共享这份RDB快照。这点Redis做了优化,有效避免了创建多个快照。但是,同时向多个从节点发送RDB快照,可能使主节 点的网络带宽消耗严重,造成主节点的延迟变大,极端情况会发生主从节点连接断开,导致复制失败
  • 解决方案有:可以减少主节点挂载从节点的数量, 或者采用树状复制结构,加入中间层从节点用来保护主节点,如下图所示

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

②单机器复制风暴

  • 由于Redis的单线程架构,通常单台机器会部署多个Redis实例
  • 当一台机器(machine)上同时部署多个主节点(master)时,如下图所示

Redis(开发与运维):37---复制之(开发与运维中的问题:读写分离(数据延迟、读到过期数据、从节点故障)、主从配置不一致、规避全量复制、规避复制风暴)

  • 如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量从节点(slave)针对这台机器的主节点进行全量复制,会造成当前机器网络带宽耗尽
  • 如何避免?方法如下:
    • 应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的 主节点
    • 当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制