理解了复制原理之后,本节我们重点分析基于复制的应用场景。通过复
制机制,数据集可以存在多个副本(从节点)。这些副本可以应用于读写分
离、故障转移(failover)、实时备份等场景。但是在实际应用复制功能
时,依然有一些坑需要跳过。
6.4.1 读写分离
对于读占比较高的场景,可以通过把一部分读流量分摊到从节点
(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行
写操作,如图6-19所示。
当使用从节点响应读请求时,业务端可能会遇到如下问题:
·复制数据延迟。
·读到过期数据。
·从节点故障。
1.数据延迟
Redis复制数据的延迟由于异步复制特性是无法避免的,延迟取决于网
络带宽和命令阻塞情况,比如刚在主节点写入数据后立刻在从节点上读取可
能获取不到。需要业务场景允许短时间内的数据延迟。对于无法容忍大量延
迟场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时
触发报警或者通知客户端避免读取延迟过高的从节点,实现逻辑如图6-20所
示。
说明如下:
1)监控程序(monitor)定期检查主从节点的偏移量,主节点偏移量在
info replication的master_repl_offset指标记录,从节点偏移量可以查询主节点
的slave0字段的offset指标,它们的差值就是主从节点延迟的字节量。
2)当延迟字节量过高时,比如超过10MB。监控程序触发报警并通知客
户端从节点延迟过高。可以采用Zookeeper的监听回调机制实现客户端通
知。
3)客户端接到具体的从节点高延迟通知后,修改读命令路由到其他从
节点或主节点上。当延迟恢复后,再次通知客户端,恢复从节点的读命令请
求。
这种方案的成本比较高,需要单独修改适配Redis的客户端类库。如果
涉及多种语言成本将会扩大。客户端逻辑需要识别出读写请求并自动路由,
还需要维护故障和恢复的通知。采用此方案视具体的业务而定,如果允许不
一致性或对延迟不敏感的业务可以忽略,也可以采用Redis集群方案做水平
扩展。
2.读到过期数据
当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维
护过期数据删除策略,删除策略主要有两种:惰性删除和定时删除,具体细
节见8.2节“内存管理”。
惰性删除:主节点每次处理读取命令时,都会检查键是否超时,如果超
时则执行del命令删除键对象,之后del命令也会异步发送给从节点。需要注
意的是为了保证复制的一致性,从节点自身永远不会主动删除超时数据,如
图6-21所示。
定时删除:Redis主节点在内部定时任务会循环采样一定数量的键,当
发现采样的键过期时执行del命令,之后再同步给从节点,如图6-22所示。
如果此时数据大量超时,主节点采样速度跟不上过期速度且主节点没有
读取过期键的操作,那么从节点将无法收到del命令。这时在从节点上可以
读取到已经超时的数据。Redis在3.2版本解决了这个问题,从节点读取数据
之前会检查键的过期时间来决定是否返回数据,可以升级到3.2版本来规避
这个问题。
3.从节点故障问题
对于从节点的故障问题,需要在客户端维护可用从节点列表,当从节点
故障时立刻切换到其他从节点或主节点上。这个过程类似上文提到的针对延
迟过高的监控处理,需要开发人员改造客户端类库。
综上所出,使用Redis做读写分离存在一定的成本。Redis本身的性能非
常高,开发人员在使用额外的从节点提升读性能之前,尽量在主节点上做充
分优化,比如解决慢查询,持久化阻塞,合理应用数据结构等,当主节点优
化空间不大时再考虑扩展。笔者建议大家在做读写分离之前,可以考虑使用
Redis Cluster等分布式解决方案,这样不止扩展了读性能还可以扩展写性能
和可支撑数据规模,并且一致性和故障转移也可以得到保证,对于客户端的
维护逻辑也相对容易。
6.4.2 主从配置不一致
主从配置不一致是一个容易忽视的问题。对于有些配置主从之间是可以
不一致,比如:主节点关闭AOF在从节点开启。但对于内存相关的配置必须
要一致,比如maxmemory,hash-max-ziplist-entries等参数。当配置的
maxmemory从节点小于主节点,如果复制的数据量超过从节点maxmemory
时,它会根据maxmemory-policy策略进行内存溢出控制,此时从节点数据已
经丢失,但主从复制流程依然正常进行,复制偏移量也正常。修复这类问题
也只能手动进行全量复制。当压缩列表相关参数不一致时,虽然主从节点存
储的数据一致但实际内存占用情况差异会比较大。更多压缩列表细节见8.3
节“内存管理”。
6.4.3 规避全量复制
全量复制是一个非常消耗资源的操作,前面做了具体说明。因此如何规
避全量复制是需要重点关注的运维点。下面我们对需要进行全量复制的场景
逐个分析:
·第一次建立复制:由于是第一次建立复制,从节点不包含任何主节点
数据,因此必须进行全量复制才能完成数据同步。对于这种情况全量复制无
法避免。当对数据量较大且流量较高的主节点添加从节点时,建议在低峰时
进行操作,或者尽量规避使用大数据量的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,从而避免因复制积
压缓冲区不足造成的全量复制。
6.4.4 规避复制风暴
复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点
短时间内发起全量复制的过程。复制风暴对发起复制的主节点或者机器造成
大量开销,导致CPU、内存、带宽消耗。因此我们应该分析出复制风暴发生
的场景,提前采用合理的方式规避。规避方式有如下几个。
1.单主节点复制风暴
单主节点复制风暴一般发生在主节点挂载多个从节点的场景。当主节点
重启恢复后,从节点会发起全量复制流程,这时主节点就会为从节点创建
RDB快照,如果在快照创建完毕之前,有多个从节点都尝试与主节点进行全
量同步,那么其他从节点将共享这份RDB快照。这点Redis做了优化,有效
避免了创建多个快照。但是,同时向多个从节点发送RDB快照,可能使主节
点的网络带宽消耗严重,造成主节点的延迟变大,极端情况会发生主从节点
连接断开,导致复制失败。
解决方案首先可以减少主节点(master)挂载从节点(slave)的数量,
或者采用树状复制结构,加入中间层从节点用来保护主节点,如图6-23所
示。
从节点采用树状树非常有用,网络开销交给位于中间层的从节点,而不
必消耗顶层的主节点。但是这种树状结构也带来了运维的复杂性,增加了手
动和自动处理故障转移的难度。
2.单机器复制风暴
由于Redis的单线程架构,通常单台机器会部署多个Redis实例。当一台
机器(machine)上同时部署多个主节点(master)时,如图6-24所示。
如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量
从节点(slave)针对这台机器的主节点进行全量复制,会造成当前机器网
络带宽耗尽。
如何避免?方法如下:
·应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的
主节点。
·当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密
集的全量复制。
6.5 本章重点回顾
1)Redis通过复制功能实现主节点的多个副本。从节点可灵活地通过
slaveof命令建立或断开复制流程。
2)复制支持树状结构,从节点可以复制另一个从节点,实现一层层向
下的复制流。Redis2.8之后复制的流程分为:全量复制和部分复制。全量复
制需要同步全部主节点的数据集,大量消耗机器和网络资源。而部分复制有
效减少因网络异常等原因造成的不必要全量复制情况。通过配置合理的复制
积压缓冲区尽量避免全量复制。
3)主从节点之间维护心跳和偏移量检查机制,保证主从节点通信正常
和数据一致。
4)Redis为了保证高性能复制过程是异步的,写命令处理完后直接返回
给客户端,不等待从节点复制完成。因此从节点数据集会有延迟情况。
5)当使用从节点用于读写分离时会存在数据延迟、过期数据、从节点
可用性等问题,需要根据自身业务提前作出规避。
6)在运维过程中,主节点存在多个从节点或者一台机器上部署大量主
节点的情况下,会有复制风暴的风险。