1. 为什么三台机器跑三主三从是个务实的选择
很多人第一次接触 Redis 集群,脑子里冒出来的第一个问题往往是"我到底需要几台机器"。官方文档给出的最小可用拓扑是三个主节点,因为集群的故障转移和投票机制依赖多数派原则,三个主节点意味着可以容忍一个主节点挂掉而不影响整个集群的可用性。但如果你只部署三个主节点,一旦某个主节点宕机,它上面的槽位虽然会被其他主节点接管,可数据副本就没了,恢复期间如果再来一次故障,整个集群就可能进入不可用状态。
所以生产环境里更常见的做法是给每个主节点配一个从节点,形成三主三从的六节点拓扑。这时候问题来了:六个节点是不是必须六台机器?答案是不一定。Redis 集群的节点是逻辑概念,不是物理概念,你完全可以在三台物理机或虚拟机上各跑两个 Redis 实例,一个当主一个当从,只要端口不冲突、内存够用、网络延迟可控,这套拓扑就能正常工作。
我选择三台虚拟机做三主三从,核心考量有三个。第一是资源成本,六台机器的开销对个人学习或者中小团队来说偏高,三台机器能把硬件利用率拉满。第二是故障域的模拟,三台机器意味着任意一台挂掉,你都能观察到集群如何重新分配槽位、如何提升从节点、如何继续对外服务,这个演练价值比六台机器分散部署要高得多。第三是配置复杂度可控,三台机器上的端口规划、防火墙规则、启动脚本都更容易管理,排查问题时思路也更清晰。
不过这里有个前提需要说清楚:三台机器跑三主三从,本质上是用"实例级冗余"换"机器级冗余"。如果一台物理机彻底宕机,它上面的一主一从会同时消失,集群的可用性会瞬间下降一个档次。所以这套方案适合学习、测试、中小规模业务,如果你的业务对可用性要求极高,还是老老实实上六台机器做真正的物理隔离。
提示:本文所有操作基于常见的 Linux 发行版和 Redis 7.x 版本,不同版本在配置文件字段和集群命令上可能有细微差异,建议先确认版本再动手。
2. 动手之前的规划:端口、目录与网络
2.1 三台机器的角色分配与端口规划
假设你手上有三台虚拟机,IP 分别是 192.168.1.101、192.168.1.102、192.168.1.103。我的习惯是给每台机器分配两个 Redis 实例,端口用 7001 和 7002,其中 7001 作为主节点的候选,7002 作为从节点的候选。但要注意,主从关系不是你在配置文件里写死的,而是由集群在运行时根据槽位分配和故障转移自动决定的。你启动的时候可以指定谁是主谁是从,但后续如果发生故障转移,角色会互换。
具体规划如下表:
| 机器 IP | 实例端口 | 初始角色 | 数据目录 | 日志目录 |
|---|---|---|---|---|
| 192.168.1.101 | 7001 | 主节点 | /data/redis/7001 | /logs/redis/7001 |
| 192.168.1.101 | 7002 | 从节点 | /data/redis/7002 | /logs/redis/7002 |
| 192.168.1.102 | 7001 | 主节点 | /data/redis/7001 | /logs/redis/7001 |
| 192.168.1.102 | 7002 | 从节点 | /data/redis/7002 | /logs/redis/7002 |
| 192.168.1.103 | 7001 | 主节点 | /data/redis/7001 | /logs/redis/7001 |
| 192.168.1.103 | 7002 | 从节点 | /data/redis/7002 | /logs/redis/7002 |
这个规划的好处是每台机器的目录结构完全一致,你写启动脚本、写监控、写备份的时候可以用同一套模板,减少出错概率。端口用 7001 和 7002 而不是默认的 6379,是为了避免和机器上可能已经存在的单机 Redis 冲突,同时也让集群节点一眼就能认出来。
2.2 系统层面的准备工作
在开始装 Redis 之前,有三件事必须先做,否则后面会踩坑。第一是关闭透明大页,Redis 官方明确建议关闭 THP,因为它会导致内存分配延迟抖动。执行命令echo never > /sys/kernel/mm/transparent_hugepage/enabled,然后把它写进/etc/rc.local保证重启后依然生效。第二是调整文件描述符限制,Redis 集群每个节点都需要维持大量连接,默认的 1024 往往不够用。在/etc/security/limits.conf里加上* soft nofile 65535和* hard nofile 65535,然后重新登录生效。第三是确认时间同步,集群节点之间靠 Gossip 协议通信,时间偏差过大会导致节点状态判断异常,用ntpdate或者chronyd把三台机器的时间对齐。
还有一点容易被忽略:三台机器之间的网络必须互通,而且不能有 NAT 转换。Redis 集群的节点会把自己的 IP 和端口告诉其他节点,如果存在 NAT,其他节点拿到的地址可能无法回连,导致集群创建失败。如果你是在虚拟机里做实验,网络模式建议用桥接或者 Host-Only,不要用 NAT。
2.3 Redis 安装与目录初始化
安装 Redis 有两种方式,一种是包管理器直接装,一种是源码编译。包管理器装出来的版本往往偏旧,而且配置文件路径和源码编译的不一样,为了后续配置清晰,我习惯用源码编译。下载 Redis 7.x 的源码包,解压后执行make和make install,然后把redis-server、redis-cli、redis-sentinel这几个二进制文件复制到/usr/local/bin下。
接下来创建目录结构。在三台机器上分别执行:
mkdir -p /data/redis/7001 /data/redis/7002 mkdir -p /logs/redis/7001 /logs/redis/7002 mkdir -p /etc/redis/7001 /etc/redis/7002/data放 RDB 和 AOF 文件,/logs放日志,/etc/redis放配置文件。每个实例一个独立目录,互不干扰。这里有个经验:不要把两个实例的数据目录放在同一个磁盘分区下如果这个分区空间紧张,因为 RDB 重写的时候会临时占用双倍空间,两个实例同时重写可能把磁盘撑爆。
3. 配置文件怎么写才不容易翻车
3.1 集群模式的核心配置项
Redis 集群模式的配置文件有几个关键字段,写错了集群根本起不来。下面是一个 7001 实例的配置模板,7002 只需要改端口和目录路径:
port 7001 bind 0.0.0.0 protected-mode no daemonize yes pidfile /var/run/redis_7001.pid logfile /logs/redis/7001/redis.log dir /data/redis/7001 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 cluster-announce-ip 192.168.1.101 cluster-announce-port 7001 cluster-announce-bus-port 17001 appendonly yes appendfilename "appendonly.aof" save 900 1 save 300 10 save 60 10000逐条解释一下。cluster-enabled yes是开启集群模式的总开关,不开这个后面所有集群命令都会报错。cluster-config-file是集群节点自动维护的配置文件,你不需要手动编辑它,但必须保证 Redis 进程对这个文件有写权限,否则集群启动时会报错。cluster-node-timeout是节点超时时间,单位毫秒,15000 意味着 15 秒内没收到心跳就认为节点可能挂了,这个值太小会导致网络抖动时频繁触发故障转移,太大则故障发现延迟高,15 秒是个比较稳妥的折中。
cluster-announce-ip和cluster-announce-port这两个字段在多网卡或者有 NAT 的环境里特别重要。如果你的机器有多个 IP,Redis 默认可能选错 IP 广播出去,导致其他节点连不上。显式指定这两个字段可以避免这个问题。cluster-announce-bus-port是集群总线端口,默认是普通端口加 10000,也就是 17001,如果你改了普通端口,总线端口也要跟着改,而且防火墙必须放行这个端口。
3.2 持久化策略的取舍
集群模式下持久化策略需要单独考虑。我上面配了 AOF 和 RDB 同时开启,这是比较保守的做法。AOF 保证数据丢失窗口小,RDB 用于快速恢复和备份。但 AOF 对性能有影响,如果你的业务对写入性能极其敏感,可以只开 RDB,或者把 AOF 的刷盘策略改成everysec而不是always。
这里有个坑:集群模式下每个节点都在写 AOF,如果某个从节点长时间和主节点断连,重新连上后会触发全量同步,这时候从节点会加载 RDB 然后重放 AOF,如果 AOF 文件很大,这个过程会非常慢,甚至导致从节点被集群踢出去。我的做法是给从节点配置appendonly no,只保留 RDB,因为从节点的数据可以从主节点重新同步,不需要本地持久化来保证数据安全。
3.3 启动实例与验证
配置文件写好后,在三台机器上分别启动两个实例:
redis-server /etc/redis/7001/redis.conf redis-server /etc/redis/7002/redis.conf启动后用ps -ef | grep redis确认进程存在,然后用redis-cli -p 7001 ping测试连通性。如果返回 PONG,说明实例正常。如果返回(error) DENIED Redis is running in protected mode,说明protected-mode没关或者bind配置不对。
这时候集群还没有创建,每个节点都是孤立的,cluster info会显示cluster_state:fail,这是正常的。接下来才是最关键的一步:用redis-cli --cluster create命令把六个节点组成集群。
4. 集群创建、槽位分配与验证
4.1 创建命令的完整写法与参数含义
创建集群的命令看起来很长,但每个参数都有明确含义:
redis-cli --cluster create \ 192.168.1.101:7001 192.168.1.102:7001 192.168.1.103:7001 \ 192.168.1.101:7002 192.168.1.102:7002 192.168.1.103:7002 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点配一个从节点。Redis 会按照你给出的顺序,把前三个当作主节点候选,后三个当作从节点候选,然后自动分配主从关系和槽位。执行后它会打印出一个分配方案,问你是否接受,输入yes回车即可。
这里有个细节:如果你希望主从关系按照你预期的配对方式(比如 101 的从是 102),需要在命令里调整节点顺序,或者创建后用cluster replicate命令手动调整。自动分配的主从关系是随机的,但通常会把从节点分配到不同的物理机上,这其实是好事,因为同一台机器的两个实例如果互为主从,机器一挂就全没了。
4.2 槽位分配的原理与验证
Redis 集群把整个键空间分成 16384 个槽位,每个主节点负责一部分。创建集群时,Redis 会尽量均匀地分配,三个主节点大概各分到 5461 个槽。你可以用cluster slots命令查看分配结果,或者用cluster nodes看每个节点的角色和槽位范围。
验证集群是否正常,有几个命令必须会:
redis-cli -p 7001 cluster info redis-cli -p 7001 cluster nodes redis-cli -c -p 7001 set foo bar redis-cli -c -p 7001 get foocluster info里cluster_state:ok表示集群健康,cluster_slots_assigned:16384表示所有槽位都已分配。cluster nodes会列出所有节点及其角色,主节点后面会标注槽位范围,从节点会标注它复制的是哪个主节点。
-c参数是集群模式的关键,它让 redis-cli 在收到 MOVED 重定向时自动跳转到正确的节点。如果你不加-c,当 key 不在当前节点时,你会收到一个 MOVED 错误,需要手动去连目标节点。生产代码里客户端库会自动处理重定向,但用命令行测试时一定要加-c。
4.3 故障转移演练:亲手挂掉一个主节点
集群建好之后,一定要做一次故障转移演练,否则你永远不知道配置是否真的可靠。找一台机器,把它的主节点进程杀掉:
redis-cli -p 7001 shutdown nosave然后立刻在另一台机器上观察cluster nodes的输出。你会看到被杀的节点状态变成fail,它的从节点会在几秒内被提升为新的主节点,槽位也会被接管。整个过程通常在 10 到 15 秒内完成,取决于cluster-node-timeout的设置。
这时候如果你用-c模式去读写原来那个主节点负责的 key,会发现请求被自动重定向到新的主节点,业务几乎无感知。这就是集群的价值所在。演练完成后,把杀掉的节点重新启动,它会自动以从节点的身份加入集群,并开始同步新主节点的数据。
注意:故障转移演练不要在业务高峰期做,虽然理论上无感知,但客户端连接池可能会有短暂的报错,需要做好重试逻辑。
5. 日常运维中那些文档不会告诉你的事
5.1 集群节点的增删与槽位迁移
业务增长后可能需要加节点,或者某个节点磁盘坏了需要替换。集群模式下增删节点不是简单改配置文件,而是要用redis-cli --cluster的一系列子命令。加主节点用add-node,然后reshard迁移槽位;加从节点用add-node --cluster-slave;删节点用del-node,但删之前必须确保它没有负责任何槽位,否则会报错。
槽位迁移是个精细活,reshard命令会交互式地问你迁移多少个槽、从哪些节点迁、迁到哪个节点。迁移过程中集群仍然可用,但涉及迁移槽位的 key 会有短暂的访问延迟。我的经验是每次迁移不超过 1000 个槽,分多次完成,避免一次性迁移太多导致网络和磁盘压力过大。
5.2 常见报错与排查思路
集群运维中遇到最多的报错是CLUSTERDOWN Hash slot not served,意思是某个槽位没有节点负责。这通常发生在集群创建不完整、节点被误删、或者槽位迁移中断的情况下。排查方法是先用cluster info看cluster_slots_assigned是否等于 16384,如果不是,用cluster nodes找出哪些槽位缺失,然后手动用cluster addslots补上,或者用reshard重新分配。
另一个高频报错是MOVED和ASK重定向。MOVED表示 key 永久迁移到了另一个节点,客户端应该更新路由表;ASK表示 key 正在迁移中,客户端应该临时去目标节点查询,但不要更新路由表。如果你用的是自己实现的客户端,这两个重定向必须正确处理,否则会出现数据读写不一致。
5.3 监控指标与容量规划
集群跑起来之后,至少要监控这几个指标:每个节点的内存使用率、连接数、命中率、慢查询数量、以及集群状态。内存使用率超过 70% 就要考虑扩容,因为 RDB 重写和主从同步都需要额外内存。连接数突然飙升可能是客户端没有正确释放连接,或者有热点 key 导致某个节点压力过大。
容量规划方面,三主三从的写入能力大致是单机的三倍,但读能力取决于从节点的数量和客户端是否开启了读写分离。如果你的业务读多写少,可以给每个主节点配两个从节点,把读流量分散出去。但要注意,从节点越多,主节点的复制压力越大,网络带宽可能成为瓶颈。
6. 把这套环境用起来:客户端接入与压测
6.1 客户端连接的正确姿势
用 Python 的 redis-py 连接集群时,要用RedisCluster类而不是普通的Redis类:
from redis.cluster import RedisCluster startup_nodes = [ {"host": "192.168.1.101", "port": 7001}, {"host": "192.168.1.102", "port": 7001}, {"host": "192.168.1.103", "port": 7001}, ] rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True) rc.set("foo", "bar") print(rc.get("foo"))注意startup_nodes只需要给部分节点,客户端会自动发现完整拓扑。但至少要给两个不同机器的节点,否则那个机器挂了客户端就连不上集群了。Java 的 Jedis 和 Lettuce 也都有对应的集群客户端类,用法类似。
6.2 用 redis-benchmark 做一轮压测
集群建好后,用redis-benchmark做一轮基准测试,心里有个底:
redis-benchmark -h 192.168.1.101 -p 7001 -c 50 -n 100000 -t set,get -q-c 50表示 50 个并发连接,-n 100000表示总共 10 万次操作,-t set,get表示只测 set 和 get,-q表示只输出结果摘要。压测时观察三台机器的 CPU 和网络,如果某个节点明显偏高,说明槽位分配不均匀或者有热点 key。
压测结果受虚拟机配置影响很大,不要拿这个数字去推断生产环境的性能。它的价值在于帮你发现配置问题,比如如果 QPS 低得离谱,可能是网络延迟太高、或者持久化策略太重、或者集群总线端口被防火墙限速了。
6.3 数据备份与恢复的实操
集群模式下的备份不能简单复制 RDB 文件,因为每个节点只负责一部分数据。正确的做法是在每个主节点上执行BGSAVE,然后把生成的 RDB 文件收集起来,恢复时分别放到对应节点的数据目录下。但要注意,如果集群拓扑变了,槽位分配和 RDB 文件可能对不上,恢复后需要重新分配槽位。
更稳妥的备份方式是用redis-cli --cluster的backup功能,或者用第三方工具做逻辑备份。逻辑备份的好处是跨版本兼容,恢复时不受槽位分配影响,缺点是速度慢、占用空间大。我的做法是每天做一次逻辑备份,每周做一次 RDB 物理备份,两者结合,兼顾恢复速度和灵活性。
7. 我在三台机器上踩过的那些坑
第一个坑是防火墙。集群总线端口 17001 默认不在常用端口列表里,很多防火墙规则只放行了 7001 和 7002,结果集群创建时节点之间无法通信,一直卡在Waiting for the cluster to join。解决办法是在三台机器上都放行 7001、7002、17001、17002 这四个端口,TCP 和 UDP 都要放行,因为集群总线同时使用两种协议。
第二个坑是内存溢出。三台虚拟机如果每台只有 2GB 内存,跑两个 Redis 实例加上系统本身,很容易触发 OOM Killer 把 Redis 进程杀掉。我的建议是每台机器至少 4GB 内存,并且给 Redis 配置maxmemory限制,比如每个实例限制 1GB,留足余量给系统和其他进程。maxmemory-policy建议用allkeys-lru,这样内存满了会自动淘汰旧数据而不是直接报错。
第三个坑是集群创建后忘记设置密码。如果 Redis 没有密码,任何能访问到端口的人都可以操作你的数据。集群模式下设置密码需要在每个节点的配置文件里加requirepass和masterauth,两个都要加,前者是客户端连接密码,后者是主从同步密码。改完密码后需要重启所有节点,然后用redis-cli -a带密码重新创建集群或者更新配置。
第四个坑是虚拟机快照恢复后集群状态异常。如果你用快照回滚了某台机器,它的集群配置文件nodes-7001.conf可能还是旧的状态,和其他节点对不上。这时候需要把该节点的nodes-*.conf删掉,重启 Redis,让它重新从其他节点同步集群状态。如果整个集群都回滚了,那只能重新创建集群。
这套三台虚拟机三主三从的环境,我从最初搭建到稳定运行,前后折腾了大概一周时间,大部分时间都花在排查网络和配置问题上。但一旦跑通,它就是一个非常趁手的实验平台,你可以在这上面演练故障转移、测试客户端兼容性、验证扩容方案,而不用担心影响生产环境。对于想深入理解 Redis 集群的人来说,亲手搭一遍比看十遍文档都管用。