
写这篇东西的起因很简单前阵子接手了一个老项目的维护用的就是Redis 4.0.1既要替换掉原有人肉重启的模式又必须在尽量小改动的前提下保证高可用。折腾了两三天把主从复制、哨兵部署、故障切换整个链路都摸了一遍也踩了几个不算冷但足够烦人的坑。这篇就把整个在Linux环境安装Redis 4.0.1并组建哨兵模式的完整过程和配置细节写出来包含我自己的实际验证记录希望给正在做同类事情的朋友省点时间。1. 为什么需要哨兵主从复制扛不住单点故障先说清楚哨兵Sentinel到底解决了什么问题。Redis的主从复制能做数据冗余主库挂了从库手里有一份完整数据理论上可以顶上。但理论上和实际上之间差了一个关键动作谁来发现主库挂了谁来决定让哪个从库转正谁来通知应用层换连接地址在只有主从复制没有哨兵的情况下这些事全部靠人工。主库宕机业务肉眼可见地报错然后运维登录服务器手工执行slaveof no one把某个从库提升为主库再手工修改应用配置把连接指向新主库重启应用。这一套流程哪怕是熟练工也得几分钟业务中断几分钟在绝大多数场景下都不能接受。哨兵Sentinel就是来解决这个自动发现故障、自动选举新主、自动通知客户端的环节。它本质上是运行在特殊模式下的Redis实例不存业务数据专职盯着主从节点的健康状况。它做的事情可以拆成三块监控Monitoring哨兵会定期向主库、从库以及其他哨兵发送心跳命令确认大家是否存活。通知Notification当某个被监控的Redis实例出问题哨兵可以通过API或其他方式通知管理员。自动故障转移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和tcltcl用于跑官方测试如果不需要可以跳过。我用的系统是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 MALLOClibc注意MALLOClibc意思是让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 v4.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_memory1同样建议写入/etc/sysctl.conf持久化。3. 主从复制的正确搭法密码、配置项与复制原理哨兵盯着的对象是主从结构所以先得把主从复制这一层搭对。如果主从一开始就不稳哨兵做得再好也白搭。3.1 假设的拓扑结构为了后面叙述方便假设我有三台机器角色分配如下节点IP角色服务端口节点A172.16.3.11主库master6379节点B172.16.3.12从库slave6379节点C172.16.3.13从库slave6379三台机器各跑一个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:slavemaster_host、master_port指向172.16.3.11:6379master_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意思是至少两个哨兵都认为主库不可用才会判客观下线。为什么不配quorum1因为一个哨兵的误判就能触发切换网络抖动一下主库就被换了对业务的影响可能比宕机本身还大。quorum2能容忍单台哨兵误判而三个哨兵还能容忍一台哨兵本身宕机。4.2 选举新主库的参考维度判客观下线后哨兵们会推举一个Leader哨兵负责执行故障转移然后从一堆从库中挑选新主库。挑选顺序大概是这样过滤掉断连、下线、被判定为主观不可达的从库。过滤掉与主库数据差异过大的从库看复制偏移量。按优先级slave-priority选值越小优先级越高为0则永远不会被选上。优先级相同再看复制偏移量偏移量越大代表数据越新。如果还一样就看运行时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同时失联这在quorum2的前提下仍然不影响故障转移正确性。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 启动顺序有讲究启动顺序看似简单实际有讲究。最佳顺序是先确保三个Redis主从节点全部在线。依次启动三个节点的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应显示2flags应为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节点的日志会看到几个关键阶段每个哨兵连续PING主库超时达到down-after-milliseconds的5000ms后日志出现sdown标记主库。哨兵们互通信息至少2个哨兵达成一致后日志出现odown。开始选举执行故障转移的Leader日志出现vote-for-leader。执行故障转移从两个从库中选出候选日志出现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 客户端怎么感知新主库故障转移是哨兵之间完成的但应用层怎么知道连接地址变了两种常见方式客户端从哨兵拿地址。应用启动时提供Sentinel节点地址列表和master-name客户端通过SENTINEL get-master-addr-by-name mymaster命令获取当前主库地址拿到后正常连接。主库发生切换后客户端重连时会再次向哨兵询问。Redis的官方客户端大多数内置了这种哨兵模式比如Java的Jedis。业务侧自己做地址发现。封装一个服务定期通过哨兵查询master地址并写入配置中心应用监听配置变化自动切换。我个人实测中Java的JedisSentinelPool用起来最直观。核心参数就是masterName必须和sentinel.conf里sentinel monitor master-name的名字完全一致大小写敏感。搭好之后故障转移期间应用最多只会在重连窗口内发生少量超时连接池自动重建后业务恢复。下面是故障转移演练的关键输出指标阶段预期耗时关键日志特征主观下线取决于down-after-millisecondssdown master mymaster客观下线秒级odown master mymasterLeader选举秒级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-port6379/tcp firewall-cmd --permanent --add-port26379/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_AUTHmymaster-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数据文件更勤。因为哨兵配置里有动态写入的集群拓扑信息每次故障转移后都会变化留一份最近版本的备份遇到极端情况回滚时非常有价值。这套配置跑了一段时间期间也经历过一次真实的主库宕机哨兵自动切换后业务恢复时间从原来人肉操作的几分钟缩短到了十几秒。希望这套东西对你有用。