☰
Redis 4.0.1哨兵模式部署实战:从主从复制到高可用故障转移
2026/10/13 21:37:34 网站建设 项目流程

写这篇东西的起因很简单:前阵子接手了一个老项目的维护,用的就是Redis 4.0.1,既要替换掉原有人肉重启的模式,又必须在尽量小改动的前提下保证高可用。折腾了两三天,把主从复制、哨兵部署、故障切换整个链路都摸了一遍,也踩了几个不算冷但足够烦人的坑。这篇就把整个"在Linux环境安装Redis 4.0.1并组建哨兵模式"的完整过程和配置细节写出来,包含我自己的实际验证记录,希望给正在做同类事情的朋友省点时间。

1. 为什么需要哨兵:主从复制扛不住单点故障

先说清楚哨兵(Sentinel)到底解决了什么问题。Redis的主从复制能做数据冗余,主库挂了,从库手里有一份完整数据,理论上可以顶上。但"理论上"和"实际上"之间差了一个关键动作:谁来发现主库挂了?谁来决定让哪个从库转正?谁来通知应用层换连接地址?

在只有主从复制没有哨兵的情况下,这些事全部靠人工。主库宕机,业务肉眼可见地报错,然后运维登录服务器,手工执行slaveof no one把某个从库提升为主库,再手工修改应用配置把连接指向新主库,重启应用。这一套流程哪怕是熟练工也得几分钟,业务中断几分钟在绝大多数场景下都不能接受。

哨兵(Sentinel)就是来解决这个"自动发现故障、自动选举新主、自动通知客户端"的环节。它本质上是运行在特殊模式下的Redis实例,不存业务数据,专职盯着主从节点的健康状况。它做的事情可以拆成三块:

  1. 监控(Monitoring):哨兵会定期向主库、从库以及其他哨兵发送心跳命令,确认大家是否存活。
  2. 通知(Notification):当某个被监控的Redis实例出问题,哨兵可以通过API或其他方式通知管理员。
  3. 自动故障转移(Automatic Failover):当主库被判定为客观下线后,多个哨兵协商选出一个从库提升为新主库,并把其他从库重新配置为跟随新主库。

所以一套标准的高可用架构是:一主二从三哨兵。主从负责数据,哨兵负责大脑和决策。

4.0.1这个版本虽然老,但哨兵机制在2.8就已经引入,3.0到4.0之间做过多轮完善,4.0.1的哨兵功能已经相当成熟。如果你因为历史项目原因只能在这版本上做高可用,完全可以放心用。这篇文章所有配置都在4.0.1上实测过,命令和输出都以这个版本为准。

2. 安装Redis 4.0.1:版本选择与编译前的系统准备

选4.0.1主要是项目锁定版本,如果是新项目我不建议再用这么老的版本,但如果你是给老系统补高可用,这篇文章的操作完全适用。需要先确认系统的编译环境。

2.1 安装依赖工具

Redis源码包编译依赖gcc和tcl(tcl用于跑官方测试,如果不需要可以跳过)。我用的系统是CentOS 7系列,直接:

yum install -y gcc gcc-c++ tcl

如果是Ubuntu/Debian系,对应命令是apt-get install -y build-essential tcl。安装完成后先用gcc --version确认版本没问题。编译Redis 4.0.1不需要多新的gcc,老的4.8.x都能编过。

2.2 下载源码包并编译

从官方下载地址拿4.0.1源码包:

cd /usr/local/src wget http://download.redis.io/releases/redis-4.0.1.tar.gz tar -zxvf redis-4.0.1.tar.gz cd redis-4.0.1

接下来是最容易出问题的一步。直接运行make时,如果系统里缺少jemalloc开发包,会报一个经典错误:

zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory

这代表编译找不到内存分配器的头文件。解决方式有两种:

一是安装jemalloc依赖,二是用Redis自带的libc内存分配器编译。我实际测试下来,最省事的是第二种:

make MALLOC=libc

注意:MALLOC=libc意思是让Redis使用系统默认的libc分配器代替jemalloc。在生产环境如果对内存分配性能有极端要求,建议还是把jemalloc装好走默认编译路径。

编译完成后,执行安装。这里我习惯指定安装前缀目录,方便后续统一管理:

make install PREFIX=/usr/local/redis

装完后,/usr/local/redis/bin下会有redis-server、redis-cli、redis-sentinel等可执行文件。验证一下:

/usr/local/redis/bin/redis-server --version

能看到类似Redis server v=4.0.1的输出就说明编译成功。

2.3 初始化目录结构与系统参数

我习惯把配置、日志、数据、PID文件都分开目录管理,避免后期排查问题时分不清文件归属:

mkdir -p /usr/local/redis/{etc,log,data,run}

还要做一个系统参数调整:关闭透明大页(THP)。这玩意在Redis官方文档里明确建议关闭,原因在于THP会让内存分配粒度变大,Redis这种频繁做小内存分配和持久化fork的程序容易因此出现延迟毛刺。临时关闭:

echo never > /sys/kernel/mm/transparent_hugepage/enabled

不能写内核参数就直接关闭。界面系统重启后会恢复,所以生产环境建议把它写入/etc/rc.local或rc.d/rc.local。另外还要调整系统vm.overcommit_memory,建议设为1,避免极端内存情况下Redis fork失败:

sysctl -w vm.overcommit_memory=1

同样建议写入/etc/sysctl.conf持久化。

3. 主从复制的正确搭法:密码、配置项与复制原理

哨兵盯着的对象是主从结构,所以先得把主从复制这一层搭对。如果主从一开始就不稳,哨兵做得再好也白搭。

3.1 假设的拓扑结构

为了后面叙述方便,假设我有三台机器,角色分配如下:

节点IP角色服务端口
节点A172.16.3.11主库(master)6379
节点B172.16.3.12从库(slave)6379
节点C172.16.3.13从库(slave)6379

三台机器各跑一个Redis实例,后面每台机器再各跑一个Sentinel实例,Sentinel端口统一用26379。

3.2 主库配置文件

先把主库的配置写出来。基于redis-4.0.1/redis.conf这份默认配置修改,核心关键项如下:

# 端口 port 6379 # 绑定内网IP和本机回环,生产环境不要裸奔在0.0.0.0 bind 172.16.3.11 127.0.0.1 # 后台运行 daemonize yes # 关闭保护模式(前提是你已经设置了密码,见下文说明) protected-mode no # 主库的访问密码 requirepass mymaster-2024 # 日志和PID文件 logfile "/usr/local/redis/log/redis.log" pidfile "/usr/local/redis/run/redis.pid" # 数据保存,开启AOF并设置每秒刷盘 appendonly yes appendfsync everysec # 数据文件目录 dir /usr/local/redis/data

关于protected-mode要多说两句。这个参数默认是yes,作用是:在没有设置密码且没有显式bind的情况下,只允许本机回环访问。如果我们不设置密码但绑定了内网IP,其他节点访问时会直接收到DENIED错误。所以生产环境最稳妥的组合是:设置requirepass,然后显式关闭保护模式,或者干脆保持默认但绑定了127.0.0.1之后通过防火墙控制。我实测下来,在同一内网环境里关闭保护模式加密码认证是最省心的。

3.3 从库配置文件

两个从库的配置和主库基本一样,区别在于多了副本声明、密码同步声明,IP和角色描述不同:

port 6379 bind 172.16.3.12 127.0.0.1 daemonize yes protected-mode no requirepass mymaster-2024 # 从库同步主库时使用的密码,必须与主库requirepass一致 masterauth mymaster-2024 replicaof 172.16.3.11 6379 appendonly yes appendfsync everysec logfile "/usr/local/redis/log/redis.log" pidfile "/usr/local/redis/run/redis.pid" dir /usr/local/redis/data

两个从库唯一不同的就是bind的IP换成本机IP。注意,4.0.x版本配置文件里复制相关的参数还是slaveof,到了5.0才改成replicaof。如果你拿新版本的配置模板套4.0.1,某些老参数会被识别但新参数可能直接报错,容易把问题带偏。

3.4 启动并验证主从同步

先把三个节点实例都启动:

/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

然后用客户端连到从库节点查看状态:

/usr/local/redis/bin/redis-cli -h 172.16.3.12 -p 6379 -a 'mymaster-2024' info replication

正常输出里role:slave,master_host、master_port指向172.16.3.11:6379,master_link_status:up。如果master_link_status显示down,多半是密码不匹配或者网络不通。用一个命令在主库写一个测试,两个从库立刻能查到的数据就说明复制链路OK。

这里说一下复制的原理,你会更清楚哪些配置是必须的。主从复制分成两个阶段:从库向主库发送PSYNC请求,主库把当前内存数据生成RDB快照发给从库,从库加载快照;在快照传输期间主库收到的写命令会写入复制积压缓冲区,快照加载完成后再把这段增量发给从库补齐。实际上两步并行推进,最终达到数据一致。masterauth就是在这个握手阶段用于认证的密码,如果不配,主库收到PSYNC请求后认为未认证直接拒绝。

4. Sentinel的职责与三个关键参数:先看懂再动手

主从复制搭好了,接下来是哨兵。动手改配置之前,先把哨兵几个最关键的设计逻辑讲明白,否则配置完遇到异常情况你根本不知道从哪里开始排查。

4.1 哨兵如何判断主库挂了

每个哨兵都会持续向被监控的主库发送PING,如果在down-after-milliseconds指定的时间内没有收到有效回复,该哨兵会把这个主库标记为主观下线(sdown)。注意,主观下线只是单个哨兵的判断,可能因为网络抖动误判。

为了避免单点误判,哨兵之间会互相通信。当标记主观下线的哨兵数量达到quorum阈值时,主库才会被标记为客观下线(odown),这时才会真正触发故障转移流程。quorum不是全部哨兵,而是至少多少个哨兵达成一致。

拿我用的三哨兵配置举例,quorum配置为2,意思是至少两个哨兵都认为主库不可用,才会判客观下线。为什么不配quorum=1?因为一个哨兵的误判就能触发切换,网络抖动一下主库就被换了,对业务的影响可能比宕机本身还大。quorum=2能容忍单台哨兵误判,而三个哨兵还能容忍一台哨兵本身宕机。

4.2 选举新主库的参考维度

判客观下线后,哨兵们会推举一个Leader哨兵负责执行故障转移,然后从一堆从库中挑选新主库。挑选顺序大概是这样:

  1. 过滤掉断连、下线、被判定为主观不可达的从库。
  2. 过滤掉与主库数据差异过大的从库(看复制偏移量)。
  3. 按优先级slave-priority选,值越小优先级越高,为0则永远不会被选上。
  4. 优先级相同再看复制偏移量,偏移量越大代表数据越新。
  5. 如果还一样,就看运行时ID,跑出唯一选择。

所以如果你有某台从库硬件特别好希望优先转正,可以把它配置文件里的slave-priority调成1,别的从库保持100默认值。这点在4.0.1实测有效。

4.3 三个核心参数怎么配

哨兵配置文件里最核心的是下面这三组:

  • sentinel monitor <master-name> <ip> <port> <quorum>:声明监控对象。master-name是逻辑名,所有哨兵必须保持一致,客户端也要用它获取地址。
  • sentinel down-after-milliseconds <master-name> <ms>:判定主观下线的等待时间。设太短,网络抖动就误判;太长,故障恢复慢。我一般设5000到10000,内网环境5000实测比较合理,跨机房可以适当加大。
  • sentinel failover-timeout <master-name> <ms>:故障转移超时,覆盖所有阶段的总时间预算。默认180000ms,我第一次配没改,后来发现极端情况故障转移拖太久,建议设成15000到30000。

还有一个经常被忽略但生产必须配的参数:sentinel auth-pass <master-name> <password>。只要主从复制设置了requirepass,哨兵连接主库也必须认证,否则哨兵会一直报-NOAUTH,看不到主库状态,故障转移根本触发不了。这个参数配错,比down-after-milliseconds配错更难排查,因为日志看起来一切正常,但状态就是上不去。

5. 三哨兵部署实操:配置明细、启动顺序与验证清单

理论说完,动手实操。我在三台节点上各放一个Sentinel实例,架构上每个节点既跑Redis又跑Sentinel。虽然同主机部署哨兵和Redis不是最佳物理隔离方案,但对于一套已有三台机器的老项目,用这个布局能最大化利用资源,副作用是如果整台机器挂掉,该节点上的Redis和Sentinel同时失联,这在quorum=2的前提下仍然不影响故障转移正确性。

5.1 Sentinel配置全文

每台机器上新建/usr/local/redis/etc/sentinel.conf,节点A的内容如下:

port 26379 daemonize yes protected-mode no logfile "/usr/local/redis/log/sentinel.log" dir "/usr/local/redis/data" pidfile "/usr/local/redis/run/sentinel.pid" # 监听主库 sentinel monitor mymaster 172.16.3.11 6379 2 # 主库密码 sentinel auth-pass mymaster mymaster-2024 # 主观下线阈值 sentinel down-after-milliseconds mymaster 5000 # 故障转移超时 sentinel failover-timeout mymaster 15000 # 并行同步新主的从库数量 sentinel parallel-syncs mymaster 1

节点B和节点C复制同样的内容,不需要改动任何参数,因为哨兵之间会自行同步监控状态。端口统一26379即可。

关于sentinel parallel-syncs,这个参数控制故障转移完成后,同时有几个从库向新主库发起同步。设成1是一个稳妥策略:新主库刚转正,如果在短时间内同时接收多个从库的全量同步,主库本身的负载和网络带宽压力会很大,可能影响正常服务。串行同步虽然慢一点,但稳。三从以上场景建议最多设2。

5.2 启动顺序有讲究

启动顺序看似简单,实际有讲究。最佳顺序是:

  1. 先确保三个Redis主从节点全部在线。
  2. 依次启动三个节点的Sentinel。

不要在Redis还没完全就绪时先启动Sentinel。虽然哨兵有重试机制,之后会自动恢复,但第一次启动时如果主库不可达,哨兵会立刻开始记录主观下线,并且把状态写进自己的配置文件里,有可能造成后续判断状态的混乱。

启动Sentinel的命令有两种写法,任选其一:

/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf

或者

/usr/local/redis/bin/redis-server /usr/local/redis/etc/sentinel.conf --sentinel

我推荐用redis-sentinel命令,语义清晰,我实际使用中没遇到过问题。

5.3 启动后立刻做的验证

这是我觉得最容易偷懒也最不应该偷懒的环节。启动完直接看Sentinel日志:

tail -f /usr/local/redis/log/sentinel.log

正常情况下会看到Sentinel从节点A和节点B发现从库节点的信息,以及每两秒一次的PING。日志里出现类似+monitor master mymaster 172.16.3.11 6379的条目,说明同步成功。

然后通过Sentinel查主库信息:

/usr/local/redis/bin/redis-cli -p 26379 sentinel master mymaster

注意,Sentinel的默认端口是26379,别拿6379去查。输出中num-slaves应显示2,flags应为master。再查从库列表:

/usr/local/redis/bin/redis-cli -p 26379 sentinel slaves mymaster

这里能看到两个从节点,各自有name、ip、port、flags:slave等字段。踩坑提示:如果flags字段带有s_down或o_down标记,说明哨兵认为节点异常,优先检查masterauth和requirepass是否匹配。

6. 故障转移演练:从主库宕机到选举恢复的全过程

配置完哨兵只是开始,真正的考验是故障转移是不是真的能用。我强烈建议每套哨兵架构上线前都做一次故障演练,趁业务低峰期人为制造主库故障,观察整个切换过程。

6.1 关闭主库模拟宕机

在节点A上直接关闭Redis主进程:

/usr/local/redis/bin/redis-cli -h 172.16.3.11 -p 6379 -a 'mymaster-2024' shutdown nosave

也可以用kill -9 主进程PID模拟更极端情况。shutdown nosave更接近正常运维操作,kill -9更接近真实崩溃,两种我都试过,哨兵对两者的处理链路没有区别,只是日志里主观下线的触发时间点略有差异。

6.2 观察哨兵状态的流转

接下来盯住任意一个Sentinel节点的日志,会看到几个关键阶段:

  1. 每个哨兵连续PING主库超时,达到down-after-milliseconds的5000ms后,日志出现sdown标记主库。
  2. 哨兵们互通信息,至少2个哨兵达成一致后,日志出现odown。
  3. 开始选举执行故障转移的Leader,日志出现+vote-for-leader。
  4. 执行故障转移,从两个从库中选出候选,日志出现+switch-master,然后附带新主库的IP和端口。

我实际演练时这部分大约耗时10秒左右,比预期的快不少。切换后立刻验证新主库:

/usr/local/redis/bin/redis-cli -h 172.16.3.12 -p 6379 -a 'mymaster-2024' info replication

如果角色已经变为role:master,说明节点B被成功提升。再看节点C,它应该已经自动执行replicaof转向节点B。

6.3 旧主恢复后自动变从

把节点A的Redis重新拉起来,此时如果一切正常,它不会再当回主库,而是检测到当前集群里已经有新主库,自动以从库身份加入:

/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

过几秒查看节点A的复制状态,会发现role:slave,主节点指向节点B。这一步是哨兵最大的价值:全程不需要人手动改任何配置文件,也不需要重启应用。

6.4 客户端怎么感知新主库

故障转移是哨兵之间完成的,但应用层怎么知道连接地址变了?两种常见方式:

  1. 客户端从哨兵拿地址。应用启动时提供Sentinel节点地址列表和master-name,客户端通过SENTINEL get-master-addr-by-name mymaster命令获取当前主库地址,拿到后正常连接。主库发生切换后,客户端重连时会再次向哨兵询问。Redis的官方客户端大多数内置了这种哨兵模式,比如Java的Jedis。

  2. 业务侧自己做地址发现。封装一个服务,定期通过哨兵查询master地址并写入配置中心,应用监听配置变化自动切换。

我个人实测中,Java的JedisSentinelPool用起来最直观。核心参数就是masterName,必须和sentinel.conf里sentinel monitor <master-name>的名字完全一致,大小写敏感。搭好之后,故障转移期间应用最多只会在重连窗口内发生少量超时,连接池自动重建后业务恢复。

下面是故障转移演练的关键输出指标:

阶段预期耗时关键日志特征
主观下线取决于down-after-milliseconds+sdown master mymaster
客观下线秒级+odown master mymaster
Leader选举秒级+vote-for-leader
切换完成秒级+switch-master mymaster 172.16.3.11 172.16.3.12
从库重新同步视数据量+slave、copy

如果某一步卡住不动,大概率是哨兵的auth-pass有问题,或者两个哨兵网络不通。排查方法后面会单独讲。

7. 生产落地注意:端口、保护模式、重启与调优边界

故障演练通过只代表基本功能可用,能不能放到生产环境扛住真实流量,还有一些容易忽略的细节。

7.1 端口与防火墙规则

要放通的端口有两组:Redis数据端口6379,哨兵通信端口26379。哨兵之间通信、哨兵连主从、客户端连哨兵,都需要这两组端口的访问权限。如果用防火墙,至少放行:

firewall-cmd --permanent --add-port=6379/tcp firewall-cmd --permanent --add-port=26379/tcp firewall-cmd --reload

如果服务器之间还套用了云安全组,记得两边都要放行,否则会出现"本地连本地没问题,跨节点死活连不上"的奇怪现象。

7.2 哨兵的配置文件会被自动改写

这是很多人第一次接触哨兵时最懵的点。哨兵启动后运行一段时间,sentinel.conf会被Redis进程自动重写,追加生成的内容包括:

sentinel known-slave mymaster 172.16.3.12 6379 sentinel known-slave mymaster 172.16.3.13 6379 sentinel current-epoch 1 sentinel myid 9f1c...(一长串ID)

这是正常现象,不是配置文件损坏。这些动态信息用于哨兵之间交换状态,哪怕你不小心删了,哨兵也会重新发现并再次写入。但这也引出一个操作规范:要修改哨兵配置,不能直接改配置文件后平滑重载,必须停下来改完再启动,否则你改的内容会被自动重写的文件覆盖掉。我见过有人改了sentinel down-after-milliseconds之后直接restart,发现改的没生效,因为旧配置在停止瞬间又被重写了。

还有一种更诡异的场景:如果你把哨兵迁移到新机器,但直接拷贝了旧sentinel.conf过去,新节点上会带着旧myid和旧known-slave,导致它和其他哨兵无法正确组队。解决方法是迁移时删掉myid行,让哨兵重新生成身份。

7.3 保护模式与密码的边界

我在第3节说过,配置里用了protected-mode no,这里再补充一个细节:如果主库本身开启了保护模式但没有设置密码,那么从库和哨兵在连接主库时会遇到DENIED Redis is running in protected mode错误。遇到这个错误时,优先排查的不是防火墙,而是主库有没有密码、哨兵有没有配auth-pass。

设置密码后的一个副作用是,redis-cli每次执行命令都要带-a参数,这会暴露密码在命令行中。如果机器有其他人能查看进程列表,可以用REDISCLI_AUTH环境变量代替:

export REDISCLI_AUTH='mymaster-2024' redis-cli -h 172.16.3.11 -p 6379 info replication

这样进程列表里不会直接出现密码明文。

7.4 数据持久化的配合策略

哨兵能保证服务不中断,但数据不能只指望内存。生产环境我建议RDB和AOF都开,RDB做快速恢复和备份,AOF做更细粒度的数据安全保障,appendfsync everysec在性能和数据安全之间平衡较好,实测4.0.1在这个配置下性能损耗可接受。

有一个实际遇到过的教训:如果AOF文件在故障转移过程中因为反复的读写而变得过大,Redis启动时加载AOF需要很长时间,这会让新主库对外提供服务的时间点延后。所以在线时长高的节点,注意监控AOF文件大小,必要时周期执行BGREWRITEAOF重写。

7.5 单机模拟哨兵模式的方法

如果手里暂时没有三台机器,也可以在一台机器上用多端口模拟整个架构,逻辑完全一致。做法是准备三个Redis实例目录,端口分别是6379、6380、6381,其中一个做主库,另外两个做从库,再起三个哨兵实例,端口分别是26379、26380、26381。配置文件里的bind全改成127.0.0.1即可。这个方式适合本地环境验证配置语法和故障转移逻辑,但生产网络环境、防火墙策略、跨机房延迟这些影响因素就测不到了。

写在最后的个人体会

整个流程走下来,我最深的感受是:Redis的哨兵机制设计得还算成熟,4.0.1版本在故障转移的核心链路上没有明显短板,真正的问题往往出在配置细节的一致性上。密码、master-name、端口、阈值这几个要素,任何一处对不上都会让你排查到怀疑人生。

给后来者三个经验性建议:

第一,部署完成不等于高可用生效,务必做至少一次真实故障演练,把日志每个阶段都看一遍。我演练时发现过哨兵把从库同步误判为超时的情况,如果没有演练直接上线,等真出故障时才发现问题就晚了。

第二,所有节点的系统时间要尽量一致,哨兵之间的时间戳判断在某些异常场景下会用来比较事件先后顺序,时间偏差过大可能影响判断结果。

第三,配置文件的备份周期要比Redis数据文件更勤。因为哨兵配置里有动态写入的集群拓扑信息,每次故障转移后都会变化,留一份最近版本的备份,遇到极端情况回滚时非常有价值。

这套配置跑了一段时间,期间也经历过一次真实的主库宕机,哨兵自动切换后业务恢复时间从原来人肉操作的几分钟缩短到了十几秒。希望这套东西对你有用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询