ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redis一主两从三哨兵架构详解:从主从复制到高可用故障转移

Redis一主两从三哨兵架构详解:从主从复制到高可用故障转移 1. 为什么是一主两从三哨兵架构设计与选型逻辑1.1 单机Redis的痛点和主从复制能解决什么先说一个很多团队都会踩的坑。业务流量刚起来的时候我们通常直接上一台Redis实例缓存、Session、热点数据全塞进去运行得很爽。直到某天凌晨内存暴涨或者磁盘出问题Redis进程直接挂掉整个服务瞬间雪崩——所有请求穿透到数据库数据库也跟着告警最后机器负载全线飘红。更难受的是单机Redis没有备份如果数据持久化没做好恢复之后缓存全空还得慢慢等流量回填这段时间用户体验基本靠祈祷。主从复制解决了一部分问题。主节点负责写从节点同步数据并提供读服务相当于给数据做了实时备份。就算主节点挂了至少从节点手里还有一份完整数据不至于一切从头再来。但主从复制有个明显的短板它不会自动切换。主节点宕机之后从节点永远是从应用也不会自动把读写请求切到从节点上还是需要人半夜起来手动执行slaveof no one或者replicaof no one把某个从节点提升为主再改一堆应用配置。这个手动过程少则几分钟多则半小时线上故障黄金恢复时间就这么被浪费掉了。1.2 哨兵解决了主从复制解决不了的问题Sentinel哨兵机制的出现就是专门解决主节点挂了我怎么办这个问题的。它做了四件事监控、通知、自动故障转移、配置中心。哨兵会持续检查主节点和从节点的健康状态一旦发现主节点不可用就按照预先定义的规则从一堆从节点里选出一个幸运儿提升为新主节点然后把其他从节点重新指到新主下面同时把新主地址通知给客户端。整个过程不需要人来介入应用侧只要支持从哨兵拿地址就能透明地完成切换。所以说一主两从三哨兵这套架构本质上是主从复制提供数据冗余 哨兵提供高可用自动切换的组合拳。它适合大多数中小型业务读多写少、对一致性要求不是极高、但又希望Redis故障后业务不中断的场景。1.3 quorum2 这个数字怎么来的三个哨兵的配置里最关键的一行是sentinel monitor mymaster master_ip 6379 2最后的2就是 quorum投票数。它的含义是当有至少2个哨兵认为主节点故障时才判定主节点真正下线才允许触发故障转移。为什么是3个哨兵因为哨兵也要开会投票投票机制要求多数派才能做决定。3个哨兵挂1个还剩2个能形成多数2个哨兵挂1个只剩1个直接脑死亡4个哨兵挂1个虽然也能投但和三哨兵容错能力一样还多了一台机器的成本和更多的网络交互不划算。所以奇数节点是最优解三哨兵既有容错能力又不会过度设计。至于一主两从一方面是为了保证故障转移后起码有一个从节点可以顶上另一方面从节点可以分摊读流量。如果只有一主一从挂掉一个从节点后读压力会全部回到主节点高可用和读写分离能力都会打折扣。2. 部署前的环境准备与版本选择2.1 版本选型7.x 和 6.x 怎么选关于Redis版本我的建议是新项目直接上 7.x老项目如果是 6.2 以上也可以继续用低于 6.0 的建议找个时间窗口升级一下。原因不复杂。7.x 在性能、ACL权限管理、AOF持久化、多线程I/O等方面都有不少改进。哨兵和集群在 7.x 上也很稳定官方对 Redis 7 的维护周期更长。6.2 作为过渡版本也不错很多生产环境还在大量运行。但 2.8、3.2、4.0、5.0 这些老版本哨兵机制虽然早就有了但一些边缘场景下的行为和现代版本差别不小排查问题时能找到的资料也少不建议新部署时再用。如果你用的是云厂商提供的Redis版本选择会更简单——选最新稳定版就好云平台一般会把底层运维都处理好。自己部署的话版本锁定在 7.0 或 7.2 这个分支比较稳妥我这次文章里以 7.0 系列为例命令和配置文件在 6.2 到 7.2 之间基本通用。2.2 节点与端口规划我建议至少准备三台机器或者用三台虚拟机/容器。生产环境不要把所有角色塞在同一台机器上因为哨兵高可用的前提就是异地主备——一台物理机挂了另外两台还能继续干活。本文规划如下节点主机IP示例Redis角色Redis端口哨兵角色哨兵端口node-110.0.0.11redis-master6379sentinel-126379node-210.0.0.12redis-slave-16379sentinel-226379node-310.0.0.13redis-slave-26379sentinel-326379每个节点的 Redis 端口都用 6379哨兵都用 26379这也是生产环境最常见的做法。如果你只想在一台机器上做实验也可以把 Redis 端口拆成 6379/6380/6381把哨兵拆成 26379/26380/26381原理完全一样只是配置里要注意端口别写错。文章的配置示例我会按照三台机器的方案来写。2.3 下载编译安装 Redis三台机器都要装 Redis。装完之后先不急配置我们先确认版本。我习惯在每台机器上编译安装一份目录统一方便后续维护脚本。编译安装步骤如下# 下载源码包版本号可到官网确认最新正式版本 wget https://download.redis.io/releases/redis-7.0.14.tar.gz tar zxvf redis-7.0.14.tar.gz cd redis-7.0.14 # 编译 make -j4 # 安装到指定目录 make install PREFIX/usr/local/redis # 验证 /usr/local/redis/bin/redis-server -v如果网络环境不方便下载源码也可以用 apt 或 yum 安装# Ubuntu/Debian apt update apt install redis-server # CentOS/RHEL yum install redis但包管理器里的版本可能比较旧而且有些发行版会把配置文件的路径打得很分散后面找日志和维护会比较痛苦。我个人还是推荐编译安装源码包里的redis.conf、sentinel.conf都在同一个目录下管理方便。安装完成后创建几个基础目录后面配置文件和日志都用得上mkdir -p /usr/local/redis/{conf,log,data}3. 一主两从主从复制部署实操3.1 三个节点的 redis.conf 关键配置主从复制的核心配置其实就三件事告诉从节点你的主是谁、设置好主从的认证信息、确保持久化开关符合需求。下面先看 master 节点的配置# /usr/local/redis/conf/redis.conf bind 0.0.0.0 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile /usr/local/redis/log/redis-6379.log dir /usr/local/redis/data requirepass Redis123456 appendonly yes这里几个关键项我解释一下bind 0.0.0.0表示监听所有网卡。如果只填127.0.0.1其他机器根本连不上从节点复制会失败。在云服务器上更严谨的做法是绑定内网IP例如bind 10.0.0.11。protected-mode yes这个参数很多人配置完连不上就抓瞎。Redis 3.2 之后默认开启保护模式如果实例绑定的不是回环地址且没有设置密码外部访问会被拒绝。我这里设置了requirepass所以 protected-mode 保持 yes 是安全的——哨兵和客户端只要带密码就能访问。requirepass主节点密码必须设置否则公网/内网环境下很容易被扫描攻击。appendonly yes开启 AOF 持久化主节点数据变化能被更好地记录。虽然 AOF 会带来一点性能开销但对于需要高可用的Redis来说这个代价值得付。再看两个 slave 节点的配置# /usr/local/redis/conf/redis.conf bind 0.0.0.0 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile /usr/local/redis/log/redis-6379.log dir /usr/local/redis/data masterauth Redis123456 replicaof 10.0.0.11 6379 replica-read-only yes appendonly yesreplicaof是 Redis 5.0 之后的写法老一点的版本写成slaveof作用一样把这个节点挂到指定主节点下面。masterauth是主节点设置了requirepass时必须配的不然从节点认证失败复制链路建立不起来。replica-read-only yes是为了防止客户端误写从节点导致主从数据不一致。提示在 Redis 5.0 及以上版本推荐始终使用replicaof和replica-read-only这套新命名官方文档也统一用这套术语。3.2 启动节点并验证主从关系三个节点配置完成后分别启动 Redis/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf启动后检查日志确认没有报错。然后在从节点上分别执行/usr/local/redis/bin/redis-cli -p 6379 -a Redis123456 info replication正常情况下master 节点会显示role:master和connected_slaves:2从节点会显示role:slavemaster_host指向 10.0.0.11master_link_status:up。如果看到master_link_status:down不要急先检查网络和密码再看日志里的具体报错。然后做一个最简单的验证在主节点写一个 key在从节点读# 在 master 上 redis-cli -p 6379 -a Redis123456 set hello redis # 在两个 slave 上 redis-cli -p 6379 -a Redis123456 get hello如果都能读到redis说明主从复制已经通了。注意从节点默认只读所以你试着在从节点执行写命令会直接报错READONLY这是正常现象恰恰说明replica-read-only生效了。3.3 关于主从复制的两个常见误区误区一复制是实时的吗严格说是异步但很快。主节点执行完写命令后会把变更记录到自己的内存复制缓冲区和 backlog 里异步发送给从节点。正常情况下延迟在毫秒级但极端情况下从节点可能出现短暂延迟所以主从复制不是强一致的。误区二从节点只是冷备吗不是。从节点可以承担读流量前提是业务能容忍数据的最终一致性。常见的做法是把报表查询、排行榜读取、非实时统计类的任务打到从节点上为原始主节点减负。但写操作永远只能走主节点。4. 三哨兵部署配置文件与启动验证4.1 sentinel.conf 逐项解读主从复制搭好之后接下来就是重头戏部署三个哨兵。每个节点上需要一份哨兵配置文件内容虽然不长但每一项都有讲究。下面以 node-1 上的 sentinel-1 为例# /usr/local/redis/conf/sentinel.conf port 26379 daemonize yes pidfile /var/run/redis-sentinel-26379.pid logfile /usr/local/redis/log/sentinel-26379.log dir /usr/local/redis/data sentinel monitor mymaster 10.0.0.11 6379 2 sentinel auth-pass mymaster Redis123456 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 1一行一行来sentinel monitor mymaster 10.0.0.11 6379 2监控一个名为mymaster的主节点初始地址是 10.0.0.11:6379最后的2就是前面说的 quorum表示至少两个哨兵同意才能判定客观下线。sentinel auth-pass mymaster Redis123456主节点设了密码哨兵去连接主从节点时也要带密码。这里的mymaster必须和上一行名称保持一致。sentinel down-after-milliseconds mymaster 5000在5秒内没收到心跳响应当前哨兵就会判定目标节点主观下线。这个值不要太短否则网络抖动会导致频繁误判也不要太长否则故障转移太慢。5-10秒是比较常见的取值。sentinel failover-timeout mymaster 15000一次故障转移的总体超时时间。包括哨兵之间选举领头哨兵、从节点被重新指向新主等多个环节如果超过这个时间还没完成当前这次转移可以中止重试。生产环境我一般给30秒左右。sentinel parallel-syncs mymaster 1故障转移完成后允许同时向新主节点发起全量复制的从节点数量。设成1表示同一时间只有一个从节点在同步避免一次性多个从节点同时全量复制把新主节点打挂。node-2 和 node-3 上的哨兵配置基本一致唯一要注意的是sentinel monitor里mymaster的主节点地址都要填最初的那个 master 地址10.0.0.11:6379而不要填本机或者从节点地址。哨兵会自动发现其他人。4.2 启动哨兵哨兵可以用两种方式启动# 方式一使用 redis-sentinel老写法 /usr/local/redis/bin/redis-sentinel /usr/local/redis/conf/sentinel.conf # 方式二使用 redis-server 加 --sentinel 参数推荐 /usr/local/redis/bin/redis-server /usr/local/redis/conf/sentinel.conf --sentinel三种机器都启动后看日志。如果日志里出现monitor master mymaster 10.0.0.11 6379说明哨兵成功开始监控。三台哨兵会自动互相发现通过 Redis 的发布订阅机制定期交换消息。启动失败的常见原因就两个一是dir配置的目录不存在哨兵启动时会要求目录必须存在二是logfile指定的目录不存在写入日志直接报错。所以配置文件里涉及到的路径最好在启动前全部创建好。4.3 怎么确定哨兵集群已经达成一致启动完别急着收工一定要验证三件事。第一用info sentinel看当前哨兵认识多少个哨兵和从节点/usr/local/redis/bin/redis-cli -p 26379 info sentinel输出里面的关键行大概是sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:namemymaster,statusok,address10.0.0.11:6379,slaves2,sentinels3只要看到slaves2和sentinels3就说明哨兵已经把主从拓扑都摸清了。如果sentinels数量不对多半是网络不同或protected-mode导致其他哨兵无法访问。第二用命令查看哨兵视角下的主节点和从节点列表redis-cli -p 26379 sentinel master mymaster redis-cli -p 26379 sentinel replicas mymaster redis-cli -p 26379 sentinel sentinels mymaster第三在任意一个哨兵节点上确认它能拿到当前主节点的真实地址redis-cli -p 26379 sentinel get-master-addr-by-name mymaster输出应该返回10.0.0.11和6379。只要这个命令能正常返回说明哨兵集群状态正常可以进入下一步故障演练了。5. 哨兵到底在干什么核心工作机制拆解5.1 三个定时任务一个都不能少很多人在面试中被问哨兵的工作原理脱口而出监控 故障转移但细问下去就卡住了。要真正理解哨兵先记住它有三个定时任务第一个定时任务每隔10秒也可以配置每个哨兵会向已知的主节点和从节点发送 INFO 命令。目的是获取最新的主从拓扑信息比如发现从节点是否新增、主节点的 replication 状态如何。这就是为什么哨兵能自动发现新从节点新加的 slave 不需要改哨兵配置。第二个定时任务每隔2秒每个哨兵会通过 Redis 的发布订阅频道__sentinel__:hello广播自己的信息同时接收其他哨兵的信息。这是哨兵之间互相发现、交换对主节点判断的关键手段。通过这个频道三个哨兵才能形成一个组织。第三个定时任务每隔1秒每个哨兵会向所有主节点、从节点和哨兵节点发送 PING 命令检查它们是否存活。这个心跳是判断主观下线的基础。理解这三个任务之后你看哨兵日志就不会觉得乱码了每条日志对应的是哪个任务发起的心里有数。5.2 主观下线与客观下线的判断链路哨兵判定主节点故障不是一拍脑袋就切换的。它分两个阶段。第一阶段主观下线sdown。某个哨兵在down-after-milliseconds配置的时间内没有收到主节点对 PING 的有效回复它就在本地把这个主节点标记为主观下线。注意主观下线只是这个哨兵自己这么认为它不会立刻触发故障转移。第二阶段客观下线odown。当哨兵判定主节点主观下线后它会通过SENTINEL is-master-down-by-addr命令询问其他哨兵你觉得主节点挂了吗当收到的确认数达到 quorum配置里的2时主节点才被标记为客观下线这时候哨兵才真正启动故障转移流程。为什么要分两步因为单独一个哨兵可能误判比如网络抖动、GC停顿、机器负载过高导致PING超时只有多个哨兵都认为挂了才更可能是真的挂了。这是高可用系统里常见的宁可多确认一次也不要盲目切换思想。5.3 故障转移完整过程从选班长到全员改口客观下线确认后系统会进入领头哨兵选举环节。可以理解成多个哨兵开会先用类似 Raft 的逻辑投票选出一个leader来主导本次故障转移。leader 选出后故障转移逻辑如下第一步在所有从节点里挑一个当新主。选人规则依次是排除那些断线时间比较长、状态不健康的从节点比较replica-priority配置这个值越小优先级越高。如果某个从节点你希望它优先被提升就把它的replica-priority配成更小的数字比较复制偏移量偏移量越大说明从节点数据越新优先选如果前面都一样再比较 runid字典序小的优先这纯粹是为了打破平局。第二步被选中的从节点执行replicaof no one把自己升级为主节点开始对外提供写入服务。第三步其他从节点收到哨兵发来的指令执行replicaof 新主IP 6379重新指向新的主节点并开始从新主同步数据。第四步原来那个挂掉的主节点哨兵会继续盯着它。只要它恢复哨兵就会命令它执行replicaof 新主IP 6379让它自动降级为从节点。整个过程不需要人工干预。5.4 脑裂与数据丢失高可用背后的一体两面哨兵也不是万能的。最常见的隐患是脑裂。什么是脑裂假设原来的主节点 10.0.0.11 和哨兵、从节点之间的网络被切断但老主本身没有宕机还在继续接收客户端写入。哨兵联系不上老主于是判定客观下线触发故障转移切了一个新主出来。过一会儿网络恢复老主发现自己在给一个不存在的主节点当主浪费感情收到哨兵通知后乖乖降级为从节点。但这个过程中老主在断网期间新写入的数据因为新主根本没有这些数据会在老主降级后直接丢失。怎么防御Redis 提供了两个参数min-replicas-to-write和min-replicas-max-lag。前者要求主节点最少要有 N 个从节点连接后者要求从节点延迟不能超过某秒数。一旦主节点发现自己的从节点不够数或者延迟太大就直接拒绝写入。也就是说当网络分裂导致老主联系不上新主和从节点时老主会拒绝写入而不是接受脏数据从而把数据丢失范围控制到最小。代价是牺牲了部分可用性换来数据一致性这是高可用系统里常见的一次取舍。6. 故障演练实战模拟主节点宕机6.1 手动宕机观察哨兵日志部署完哨兵如果不做一次真实的故障演练永远不知道配置有没有问题。所谓纸上得来终觉浅绝知此事要躬行。我是强烈建议所有运维和开发同学在测试环境至少完整演练一次主节点宕机把整个转移链路看熟。在最开始的主节点上执行redis-cli -p 6379 -a Redis123456 shutdown nosave模拟主节点进程直接消失。然后到哨兵节点上观察日志tail -f /usr/local/redis/log/sentinel-26379.log你会看到事故发生的完整报道。大致是这样的节奏# 发现主节点心跳超时主观下线 sdown master mymaster 10.0.0.11 6379 # 多个哨兵确认客观下线 odown master mymaster 10.0.0.11 6379 #quorum 2/2 # 选举出了哨兵leader try-failover master mymaster 10.0.0.11 6379 switch-master mymaster 10.0.0.11 6379 10.0.0.12 6379 # 其他从节点被重新指向新主 slave-reconf-sent slave ... 10.0.0.13 6379 ...看到switch-master这一行说明故障转移已经完成新主被锁定为 10.0.0.12 了。整个过程在默认配置下大约15秒到30秒内完成实际时间取决于down-after-milliseconds和failover-timeout的调节。6.2 验证新主与从节点重定向故障转移后立刻执行redis-cli -p 26379 sentinel get-master-addr-by-name mymaster如果看到返回10.0.0.12和6379说明哨兵已经对外宣告新主地址。客户端后续拿到的都是这个地址。然后分别到新主和存活的从节点上执行info replication新主原来 node-2 上那个 slave应该显示role:master另一个从节点应该显示master_host:10.0.0.12在新主上写一个验证 key再在从节点读确认复制方向已更新。这里有个很容易忽略的点应用如果连接的是 Redis 哨兵模式而不是固定IP切换后应用才会自动感知新主。如果应用里直接写死了老主IP那么故障转移后应用依然会连到那个IP除非老主恢复否则连接必然失败。所以接入哨兵时客户端推荐使用支持哨兵模式的库比如 Java 的 Lettuce/JedisSentinelPoolGo 的 go-redis 支持按哨兵地址自动发现主从。6.3 旧主恢复后会自动降级为从节点吗故障演练的最后一步是把刚才模拟宕机的老主恢复起来看看它会变成什么样。重新启动 redis-server 后观察日志。正常情况下哨兵会发现老主上线然后自动给它下发replicaof命令让它变成新主的从节点。这个时候老主并不骄傲老老实实降级做备份。启动老主/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf等几秒后在老主上执行info replication如果看到role:slave和master_host:10.0.0.12就说明降级成功。之后老主的数据会从新主全量或者增量同步回来最终和当前主节点保持一致。有一点要注意如果旧主在宕机期间产生的数据和新主的数据存在大量冲突比如脑裂场景那么旧主降级后它的部分数据会被新主数据覆盖。所以在设计业务时不要天真地以为Redis挂了重启就能双向合并数据。Redis的复制方向是单向的、以主节点为准的这一点必须让开发和测试团队都明确知道。7. 上线前必须调整的细节与常见坑7.1 三个时间参数怎么调才合理三个容易搞混的参数down-after-milliseconds、failover-timeout、parallel-syncs。down-after-milliseconds决定故障发现快慢。设太短比如1秒短暂网络抖动就可能触发误切反而制造事故设太长Redis已经挂了业务还在等哨兵反应违背高可用初衷。我一般建议根据网络质量选择 5秒到10秒。failover-timeout决定故障转移的整体超时。如果拉得太长主节点挂了之后客户端会长时间等待重连如果太短恰好赶上从节点全量复制慢可能会被中断导致切换失败。10秒到30秒是常见区间。parallel-syncs影响切换后从节点的收敛速度。设成1最稳妥避免多个从节点同时全量复制把新主节点的CPU和网络打爆如果从节点不多、网络也够好可以设成2加快集群恢复时间。调优没有标准答案一切取决于你的网络稳定性、Redis节点规格、业务对故障时间的容忍度。唯一正确的做法是先按保守值上线再到测试环境反复演练观察实际切换耗时再做微调。7.2 云服务器环境最容易踩的三个坑第一个坑是bind和protected-mode。云服务器上如果 Redis 只监听了127.0.0.1从节点和哨兵都连不上直接表现为主从复制失败、哨兵日志不断报连接超时。需要确认bind包含服务器的内网IP或0.0.0.0同时给 Redis 设置密码。有的同学图省事直接把protected-mode no一关这是非常危险的操作——内网一旦被扫描到无密码Redis基本等于数据裸奔轻则被勒索病毒加密重则后续一堆麻烦。建议永远不要关闭 protected-mode宁可配置密码、ACL和防火墙。第二个坑是安全组和防火墙。云服务器往往还套一层安全组光在系统里开放端口没用安全组没放行照样连不上。需要同时放行6379Redis 主从和26379哨兵的 TCP 端口。为了安全应该只放行公司出口IP或者VPC网段不要对0.0.0.0/0开放。第三个坑是哨兵自动发现但地址不对。在多网卡、NAT或者Docker环境下哨兵广播的地址可能是内网IP、也可能是容器IP如果其他哨兵根据广播地址连不上就会导致sentinels数始终凑不齐。Redis 提供了sentinel announce-ip和sentinel announce-port配置手动指定哨兵对外宣称的IP和端口遇到这种场景一定要用起来。7.3 日常排错命令速查我把自己平时排查哨兵集群问题最常用的几条命令整理成一张表建议收藏目的命令查看哨兵视角的集群概览redis-cli -p 26379 info sentinel查看当前主节点地址redis-cli -p 26379 sentinel get-master-addr-by-name mymaster查看主节点详细信息redis-cli -p 26379 sentinel master mymaster查看从节点列表redis-cli -p 26379 sentinel replicas mymaster查看哨兵列表redis-cli -p 26379 sentinel sentinels mymaster强制触发故障转移测试用redis-cli -p 26379 sentinel failover mymaster查看主从复制状态redis-cli -p 6379 info replication注意sentinel failover mymaster在不需要真实宕机时也可以手动触发切换非常适合做演练比直接 kill Redis 进程温和多了。切换完成后集群拓扑会自动更新不需要额外操作。7.4 一点实际体会写到最后分享一些个人经验。我见过不少团队把哨兵部署下去就以为万事大吉结果Redis进程确实能自动切换了业务却依然大量报错。原因无非是下面几个中招客户端没有用哨兵模式连接字符串里的密码没配置对应用里缓存的key有大量热key全量缓存穿透直接打挂数据库。所以说哨兵只是高可用链路里的一环不是银弹。另一个心得是部署哨兵并不难难的是持续验证。我现在的习惯是每两周或者每次Redis版本升级后在测试环境手动跑一次sentinel failover mymaster看看切换耗时有没有变化、日志里有没有新的异常。第一次真正故障发生时你会发现之前所有演练的功夫都用得上。这套一主两从三哨兵架构配合扎实的演练习惯足以支撑绝大多数业务场景的Redis高可用需求。
返回列表