ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Redis集群从零部署实战:版本选型、故障切换与扩容排障全指南

2026/9/17 3:29:38 拓冰建站 浏览量
Redis集群从零部署实战:版本选型、故障切换与扩容排障全指南 做后端开发和运维这些年我最大的感受是Redis 集群这个话题属于“看着资料不少真上手到处是坑”的类型。很多团队一开始用单机 Redis等流量上来以后内存不够、主从切换不灵活、扩容要靠改代码重连这些问题一个个冒出来于是把 Redis 从单机迁到集群模式几乎成了必经之路。这篇内容我打算以一个老运维的视角完整复盘 Linux 环境下从零部署一套 Redis 集群的整个过程覆盖版本选型、环境准备、配置编写、创建集群、故障切换演练、扩容缩容以及高频排障。不管你是刚接触 Redis 的开发者还是正在做架构升级的运维按着这套流程走一遍应该能少踩不少坑。1. 为什么要用 Redis 集群从单机到集群的必然路径1.1 单机 Redis 的瓶颈到底在哪单机 Redis 的问题本质上可以归结为三个维度容量、可用性和性能。先说容量。一台服务器的物理内存终归是有限的Redis 作为基于内存的数据存储数据量一旦超过服务器内存能承受的上限系统就会变得极其不稳定。你当然可以通过配置 maxmemory 限制 Redis 能用的内存但限制了之后新增数据就只能走淘汰策略对业务来说这和丢数据差不了太多。另一个办法是换更大内存的服务器可 512GB 内存的机器价格翻好几倍而且单机内存规格总有天花板业务增长期靠换机器不是可持续发展的方案。再说可用性。单机 Redis 一旦宕机所有依赖缓存的请求会直接打到数据库数据库抗不住就会引发连锁故障。哪怕你手动做了主从复制主节点挂掉之后还需要人工去执行 SLAVEOF NO ONE 把从节点提升为主节点这个过程耗时越长业务受影响的时间就越长。凌晨三点被电话叫起来手工切换经历过的人都懂。最后是性能。Redis 的命令执行是单线程的虽然 Redis 6.0 之后引入了 IO 多线程来处理网络读写但核心命令执行仍然是单线程模型。单实例的处理能力受限于单核 CPU如果业务请求量巨大单个 Redis 实例会慢慢逼近性能天花板。而且随着数据量增大RDB 持久化时的 fork 耗时会变长也有造成服务卡顿的风险。1.2 集群解决了哪些问题Redis 集群模式Cluster正是冲着上面这三个问题来的。首先是水平扩展。集群把数据分散到多个主节点上每个主节点负责一部分数据。当数据量增长时往里加节点然后重新分配数据槽位就行不需要停服也不需要业务方大改代码。其次是高可用。每个主节点可以挂一个或多个从节点主节点挂了从节点会自动晋升为新的主节点业务无感运维人员不用大半夜爬起来手工切换。再次是性能分摊。读写请求会分散到不同节点虽然单节点依然是单线程执行命令但整体吞吐量会随着节点数增加而同步上涨。需要注意的是Redis 集群并不是简单的主从复制 哨兵。哨兵模式解决的是主从自动切换的问题但它本身不解决数据水平扩展的问题所有数据仍然在同一套主从结构上。集群模式则是把数据切成很多片每个片独立做主从复制和故障转移哨兵那套机制在集群里被内置实现了。1.3 集群模式背后的设计哈希槽、主从与 GossipRedis 集群的数据分布是通过哈希槽hash slot来实现的。整个集群固定划分成 16384 个槽位每个 key 通过 CRC16 算法算出一个 16bit 的哈希值再对 16384 取模得到这个 key 属于哪个槽位。集群创建时这些槽位会被均匀分配到各个主节点上。一个 key 存到哪个节点跟 key 本身的名字没有直接关系跟 key 算出来的槽位有关系。所以你在集群模式下执行 SET foo bar客户端会先算一下 foo 落在哪个槽位然后连接负责那个槽位的节点去执行。key 分散在多个节点这就是水平扩展的基础。每个主节点下还可以挂从节点。从节点实时同步主节点的数据变更一旦主节点被判定为不可用从节点会发起选举竞选成功后就升级为新的主节点。集群节点之间通过 Gossip 协议互相传递状态信息端口上默认是服务端口加 10000比如 Redis 服务端口是 6379那集群内部通信的端口就是 16379。这个端口经常被防火墙忽略后面排查问题时会专门讲到。2. 部署前准备版本选型、节点规划与环境调优2.1 节点规划与资源评估集群最小规模是 3 个主节点因为至少要有 3 个主节点才能组成一个可用集群。为了保证高可用每个主节点至少要配一个从节点所以最常见的最小部署是 3 主 3 从共 6 个节点。节点怎么规划跟你手里的服务器数量直接相关。如果是 3 台物理机或者虚拟机建议每台机器部署两个 Redis 实例一个主一个从但主从关系必须打散。比如机器 A 上放 master1 和 slave2机器 B 上放 master2 和 slave3机器 C 上放 master3 和 slave1。这样任何一台机器宕机集群里最多丢失一个主节点而它的从节点在其他机器上可以正常完成故障转移。如果图省事把某个主节点和它的从节点放在同一台机器上那这台机器一挂这个主节点和它的备份同时没了集群就会直接失去这个主节点的服务是要出大事的。内存和 CPU 的资源评估有一个简单的心算法先估算业务需要缓存的总数据量除以主节点数量就得到每个主节点上预估的数据量。每个 Redis 实例的 maxmemory 至少要大于这个值同时要小于服务器物理内存减去系统和其他进程占用后的剩余量。如果准备在一台服务器上跑两个 Redis 实例那么两个实例的 maxmemory 加在一起建议不要超过物理内存的 70%留出给系统内核页缓存和 fork 临时内存的余量。2.2 版本选型Redis 5.x、6.x、7.x 该选哪个版本选择上我个人的建议是新项目直接上 7.0.x 稳定版老项目从 5.x 或 6.x 迁移的话也优先考虑 7.0.x因为很多优化点很有吸引力。从集群功能的完整度来看Redis 5.0 是一个分水岭。5.0 之前创建集群要用外部的 redis-trib.rb 脚本那个脚本依赖 Ruby 环境部署时得多折腾一层。5.0 之后集群创建和管理功能直接做进了 redis-cli命令就是 redis-cli --cluster 子命令简单了很多这也是为什么现在大部分教程都基于 5.0 以上版本。Redis 6.0 引入了多线程 IO、ACL 权限控制和 SSL 加密传输对于安全要求高的生产环境非常有用。Redis 7.0 进一步改进了 AOF 持久化机制引入了 Multi Part AOF可以减小 AOF 重写时对内存和磁盘的压力同时优化了内存碎片整理。如果你没有特别的兼容性限制直接选 7.0 的最新小版本比如 7.0.12 或更新版本体验会好很多。2.3 环境检查与系统参数调优正式安装 Redis 之前我习惯先调整几个 Linux 内核参数虽然不调也能跑但调完之后集群在极端场景下要稳不少。下面这几个是相对关键的。# 内存分配策略避免后台持久化 fork 时被系统杀掉 sysctl -w vm.overcommit_memory1 # 关闭透明大页防止 fork 时内存开销暴涨 echo never /sys/kernel/mm/transparent_hugepage/enabled # 增大 TCP backlog高并发连接场景下减少丢连接 sysctl -w net.core.somaxconn1024vm.overcommit_memory 设置为 1含义是内核总是允许内存超额分配不要做严格检查。Redis 在做持久化时会 fork 子进程如果系统内存分配策略太保守可能在 fork 时直接被判定失败导致主进程崩溃。关闭透明大页THP同样是为了减少 fork 时的内存开销。THP 开启时内存页会变成 2MB 的大页fork 时复制页表的开销会显著增大在数据量大的实例上甚至会造成秒级卡顿。另外还要检查一下防火墙。集群节点之间需要通信的端口有两段一段是客户端连接的端口比如 6379另一段是集群内部通信的集群总线端口也就是客户端端口加 10000比如 16379。如果这两段端口没有放行集群节点之间互相发现不了建集群时就会一直卡在 Waiting for the cluster to join。还有一个容易被忽略的是文件描述符限制。Redis 单实例在连接数多的时候文件描述符消耗很快。建议把 ulimit -n 至少调到 65535。如果是通过 systemd 管理 Redis 服务需要在 service 文件里加 LimitNOFILE65535光在 shell 里 ulimit 是不生效的。3. Redis 集群一步一步搭起来3.1 下载、编译与安装 Redis我用源码编译的方式安装这样版本可控制编译参数也能按需调整。先下载 Redis 源码包官方下载地址是 download.redis.io如果服务器访问官方源慢可以换国内镜像源。cd /usr/local/src wget https://download.redis.io/releases/redis-7.0.12.tar.gz tar xzf redis-7.0.12.tar.gz cd redis-7.0.12 make -j4 make install PREFIX/usr/local/redismake install 之后二进制文件会装到 /usr/local/redis/bin 目录下里面有 redis-server、redis-cli、redis-check-aof、redis-check-rdb 等工具。装完以后把 /usr/local/redis/bin 加进 PATH方便后续操作。echo export PATH/usr/local/redis/bin:$PATH /etc/profile.d/redis.sh source /etc/profile.d/redis.sh3.2 节点配置文件的编写我用的演示拓扑是 3 台服务器每台服务器跑两个 Redis 实例一个主一个从。三台机器的 IP 假定为 192.168.10.11、192.168.10.12、192.168.10.13每台机器上的两个实例分别监听 6379 和 6380 端口总共 6 个节点。先创建目录结构。每台机器上执行mkdir -p /data/redis-cluster/6379 mkdir -p /data/redis-cluster/6380 mkdir -p /usr/local/redis/logs6379 实例的配置文件 /data/redis-cluster/6379/redis.conf 内容如下port 6379 bind 0.0.0.0 protected-mode no daemonize yes pidfile /var/run/redis_6379.pid logfile /usr/local/redis/logs/redis_6379.log dir /data/redis-cluster/6379 cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 appendonly yes appendfilename appendonly-6379.aof maxmemory 2gb maxmemory-policy allkeys-lru配置里有几个关键点我逐个说一下。cluster-enabled yes 是开启集群模式这是最核心的一项。cluster-config-file 指定集群状态文件Redis 会把当前节点的集群信息写进这个文件文件位置相对于 dir 配置的目录。cluster-node-timeout 是最重要的集群参数之一表示节点被判定为不可达的超时时间单位是毫秒。这里配置的 15000 表示 15 秒。超时时间设太短网络稍微抖动一下就可能触发主从切换设太长主节点宕机后恢复时间也会变长业务受影响的时间就久。个人经验是 15000 毫秒是个比较平衡的值。maxmemory 配置的是这个实例可以使用的最大内存。如果配置了 2gb超过之后就按 maxmemory-policy 设定的淘汰策略清理数据。这里用的是 allkeys-lru也就是在所有 key 中按 LRU 算法淘汰最近最少使用的 key。如果你的集群是纯缓存场景这个策略很合适如果集群里存的是不能随便丢的业务数据那 maxmemory 就要用心衡量配合 noeviction 策略宁可报错也不能默默淘汰数据。如果集群需要开启密码认证需要多加两个配置项requirepass Redis123456 masterauth Redis123456requirepass 是客户端访问当前节点时需要的密码masterauth 是当前节点作为从节点时连接主节点进行复制时使用的密码。这两个必须同时配置否则故障转移时新晋级的从节点没法通过认证去同步数据主从复制会一直报错。6380 实例的配置和 6379 基本一样只需要把端口、pidfile、logfile、dir、cluster-config-file、appendfilename 里的 6379 全部替换成 6380。我通常会写一个简单的模板脚本批量生成不容易漏项。3.3 启动所有节点节点配置完成后逐个启动 Redis 实例/usr/local/redis/bin/redis-server /data/redis-cluster/6379/redis.conf /usr/local/redis/bin/redis-server /data/redis-cluster/6380/redis.conf三台机器都执行完以后检查一下进程状态ps -ef | grep redis-server输出里应该能看到 6 个 redis-server 进程。此时集群还没创建节点虽然启动了但都还是孤立状态。用 redis-cli 连上去看集群信息会看到 cluster_state:fail这是正常的因为槽位还没分配。redis-cli -p 6379 cluster info3.4 用 redis-cli 创建集群接下来是核心一步。在任意一台机器上执行 redis-cli --cluster create 命令把所有节点一次性带进来。/usr/local/redis/bin/redis-cli --cluster create \ 192.168.10.11:6379 192.168.10.11:6380 \ 192.168.10.12:6379 192.168.10.12:6380 \ 192.168.10.13:6379 192.168.10.13:6380 \ --cluster-replicas 1--cluster-replicas 1 的意思是每个主节点配 1 个从节点。redis-cli 会自动分配哪些节点当主哪些节点当从并给出一个槽位分配计划。执行后屏幕会输出主从分配方案和每个节点负责的槽位范围然后问你 Can I set the above configuration?输入 yes 确认。如果集群设置了密码创建命令也要带上 -a 参数/usr/local/redis/bin/redis-cli -a Redis123456 --cluster create \ 192.168.10.11:6379 192.168.10.11:6380 \ 192.168.10.12:6379 192.168.10.12:6380 \ 192.168.10.13:6379 192.168.10.13:6380 \ --cluster-replicas 1创建完成后redis-cli 会输出 [OK] All 16384 slots covered 的提示看到这句话就表示集群已经可用了。3.5 验证集群状态与数据读写集群创建完成先看整体状态。redis-cli -p 6379 cluster info输出中 cluster_state 应该显示 okcluster_slots_assigned 显示 16384cluster_known_nodes 显示 6。再看节点详情redis-cli -p 6379 cluster nodes输出会很长每一个节点一行格式大致是节点ID 地址 角色 状态 槽位角色字段里有 master 和 slave连接状态一般是 connected。master 节点的行末尾会列出它负责的槽位范围slave 节点行里能看到它跟随的主节点 ID。验证数据读写时记得加 -c 参数也就是集群模式。如果不加 -credis-cli 不会自动帮你做节点跳转SET 时如果 key 的槽位不在当前节点上会直接报 MOVED 错误。redis-cli -c -p 6379 127.0.0.1:6379 SET user:name zhang如果 user:name 这个 key 的槽位被分配到另一台机器上你会看到类似这样的输出- Redirected to slot [14390] located at 192.168.10.13:6380 OK这表示客户端已经被自动引导到了正确的节点去执行命令。再用 GET 取一下也是同样效果。到这里一个最简单的 3 主 3 从集群已经跑起来了。4. 集群高可用实测故障切换和扩容缩容全过程4.1 模拟主节点宕机观察自动故障转移集群搭完不能只看状态还得真的把主节点搞挂一次确认它在无人干预的情况下能自动恢复服务。先看当前集群里哪个节点是主节点以及它的从节点是谁。redis-cli -p 6379 cluster nodes | grep master选定一个主节点比如 192.168.10.12:6379我们把它 kill 掉。pid$(redis-cli -h 192.168.10.12 -p 6379 info server | grep process_id | awk -F: {print $2}) kill -9 $pidkill 之后这个主节点对应的从节点会开始发起选举。cluster-node-timeout 配置的是 15000 毫秒也就是说正常情况下大约 15 秒到 20 秒之间从节点就会完成晋升。等待几十秒然后重新查看集群节点状态sleep 20 redis-cli -p 6379 cluster nodes这时能看到 192.168.10.12:6379 原本的从节点变成了 master而原来那个被杀掉的主节点变成了 fail 状态。接下来把旧主节点拉起来。重新启动 Redis 服务/usr/local/redis/bin/redis-server /data/redis-cluster/6379/redis.conf启动后旧主节点会以从节点的身份自动加入集群并开始跟随新的主节点做复制。你会发现节点角色从 master 变成了 slave状态恢复成 connected。整个过程没有人工干预集群始终保持可用。这里有一个关键经验如果配置了 requirepass 但没有配置 masterauth杀掉主节点后从节点虽然能晋升为新的主节点但旧主节点恢复后没法通过认证跟新主节点建立复制关系会一直处于握手失败状态。所以密码场景下masterauth 一定不能省。4.2 集群扩容加入新的主节点和从节点业务量增长后需要扩容Redis 集群支持在线添加节点。我先演示添加主节点。假设新机器是 192.168.10.14在上面安装 Redis创建实例 6379主和 6380从配置方法跟前面一样。启动两个实例后先把 6379 这个空节点加进集群。redis-cli -p 6379 --cluster add-node 192.168.10.14:6379 192.168.10.11:6379命令格式是 add-node 新节点地址 集群中任意一个已存在节点的地址。执行成功后新节点会以主节点身份加入集群但此时它身上没有任何槽位也就没有数据。可以用 cluster nodes 确认。让新主节点真正承担数据需要把一部分槽位从老节点迁移过来。这一步用 reshard 子命令redis-cli -p 6379 --cluster reshard 192.168.10.14:6379 \ --cluster-from c8d3e5f... \ --cluster-to 新主节点的ID \ --cluster-slots 4096--cluster-from 指定从哪个节点迁出槽位可以写多个节点 ID用逗号分隔--cluster-to 指定接收槽位的节点 ID--cluster-slots 指定要迁移多少个槽位。如果不确定节点 ID先用 cluster nodes 查一下。如果希望从所有老主节点均匀迁槽位可以把 --cluster-from 写成 all。迁移过程中 Redis 集群会正常对外服务只是对应槽位上的 key 会逐步搬过去期间访问这些 key 会有额外的重定向开销但不会中断。添加从节点的方式redis-cli -p 6379 --cluster add-node 192.168.10.14:6380 192.168.10.11:6379 \ --cluster-slave --cluster-master-id 目标主节点ID加上 --cluster-slave 参数表示新节点作为从节点加入--cluster-master-id 指定它要跟随的主节点 ID。执行完成后新从节点会开始从主节点同步数据。4.3 集群缩容安全摘除节点缩容比扩容多一步主节点必须先清空槽位再摘除从节点可以直接摘除。直接删除一个身上还有槽位的主节点是不允许的集群会报错。先摘从节点redis-cli -p 6379 --cluster del-node 192.168.10.14:6380 从节点的ID然后摘主节点。先把主节点上的槽位移走再执行删除。redis-cli -p 6379 --cluster reshard 192.168.10.14:6379 \ --cluster-from 待删除主节点的ID \ --cluster-to 接收节点的ID \ --cluster-slots 4096槽位数量填的是这个主节点当前负责的槽位数。迁移完成后用 cluster nodes 确认这个节点已经没有任何槽位再执行redis-cli -p 6379 --cluster del-node 192.168.10.14:6379 待删除主节点的ID整个缩容过程中被迁移的只是槽位上的数据其他节点的服务不受影响。但迁移大量槽位时会产生不小的网络和 CPU 开销建议选在业务低峰期操作。5. 高频故障与排查方法踩坑实录5.1 集群一直停在 Waiting for the cluster to join执行 redis-cli --cluster create 后长时间卡在 Waiting for the cluster to join这是建集群时最常见的故障。原因大多是两种一是集群总线端口16379 等被防火墙拦截节点之间收不到 Gossip 消息二是前面提到的 bind 配置有问题节点之间无法通过 IP 互相访问。排查思路很简单先确认集群节点之间的基础网络连通性用 telnet 或者 nc 检测端口。telnet 192.168.10.12 16379如果端口不通去防火墙里放行对应端口同时检查云安全组是否也放行了。如果是 bind 0.0.0.0 配置没生效节点日志里通常会有拒绝连接的记录调整配置后重启实例再试。5.2 集群状态变成 fail报 CLUSTERDOWNcluster_state:fail 意味着集群中有槽位没有节点在服务。常见原因有几个主节点挂掉且它的从节点没有完成晋升比如从节点也挂了或者配置了 requirepass 但 masterauth 缺失导致复制失败再比如超过一半的主节点同时不可达集群会停止服务以保护数据一致性。处理方式先看 cluster nodes 输出确认哪些节点处于 fail 或 disconnected 状态把宕机的进程拉起来。如果某个主节点恢复后集群状态仍然一直 fail很可能是它无法跟自己的从节点建立复制关系。这时要检查日志里是否有 authentication 相关的报错尽快补齐 masterauth 配置。5.3 客户端一直报 MOVED 或 ASK 错误在集群模式下客户端拿到 key 之后如果发现负责这个槽位的节点不是当前连接的节点会返回 MOVED 错误并附上目标节点地址。这是集群协议的正常行为但前提是客户端要能自动处理这种重定向。如果你用的是不支持集群协议的客户端或者没有按集群模式创建客户端连接就会出现明明服务端正常客户端却一直报错的情况。解决方法一是把客户端换成支持 Redis Cluster 协议的版本比如 JedisCluster、Lettuce 或者 go-redis 的集群客户端二是临时排查时用 redis-cli -c 模式。生产环境不建议依赖普通客户端的自动重试去硬扛应该在客户端侧就正确配置集群节点列表。5.4 主从切换后数据同步报错故障转移完成后新晋升的主节点开始接收写入旧主节点恢复后作为从节点去同步数据。如果配置了密码保护并且没有设置 masterauth就会看到类似 MASTER aborted replication with error: ERR Unauthorized 之类的日志。每台机器的每个节点都要配置 masterauth而且密码必须和主节点的 requirepass 一致。还有一种情况是旧主节点恢复后它的数据集和新的主节点已经不一致。比如故障期间旧主节点还在运行继续接收了写入这种情况一般只在网络分区而非进程崩溃时出现那么它重新加入集群时会尝试进行一次全量同步来对齐数据。如果网络状况很差全量同步反复失败就要检查主从之间的网络延迟和 RDB 传输大小。5.5 槽位迁移过程中出现访问超时reshard 迁移期间某些 key 被从源节点搬到目标节点客户端在迁移窗口内访问这些 key 时可能会收到 ASK 错误。支持集群协议的智能客户端会正确处理但如果不支持的客户端就会表现为访问超时或者报错。所以生产环境做扩容或缩容前要先确认客户端版本对集群重定向的支持情况并且尽量控制单次迁移的槽位数量不要一口气迁几千个槽位。我习惯每次迁 500 到 1000 个槽观察集群负载和客户端报错情况稳定后再继续。6. 集群上线前的几个重要提醒6.1 多 key 操作和 hash tag 的坑集群模式和单机 Redis 最大的区别之一就是多 key 操作的受限。单机上一条 MSET 可以一次写多个 key或者在一个 Lua 脚本里多次读写不同 key集群模式下这些 key 如果不在同一个槽位操作就会直接失败。解决办法是强制业务 team 使用 hash tag。Redis 在计算哈希槽时如果 key 里包含花括号它会只对花括号内的内容做 CRC16 计算。比如 key 写成 user:1000:profile 和 user:1000:orders在普通情况下可能落在不同槽位但如果写成 {user:1000}:profile 和 {user:1000}:orders两个 key 就会落在同一个槽位。凡是要一起操作的数据设计 key 时就要提前规划好 hash tag否则等业务上线后再改 key 格式工作量会非常大。6.2 上线前做一次故障切换演练我强烈建议集群上线之前挑一个业务低峰期做故障切换演练。不要等到线上出问题才验证。演练内容可以很简单依次 kill 掉每个主节点观察对应的从节点能否在预期时间内完成切换业务读写是否正常。如果发现某个主节点切换后它的从节点迟迟没有被提拔大概率是集群配置有问题趁早排查比线上踩雷强得多。演练完之后把 Redis 进程重新拉起确认所有节点都恢复 connected 状态复制关系正常再宣布集群真正可用。这个流程走一遍后面运维心里就有底了。6.3 监控和日常运维的几个提醒生产环境的集群一定要有监控。至少要把 cluster_state 作为核心指标盯起来一旦变成 fail 立刻告警。节点内存、命中率、主从复制延迟、慢查询日志都要纳入监控范围。常见的做法是用 redis_exporter 暴露 Metrics配合 Prometheus 和 Grafana 展示告警规则按集群角色分别设置。还有一点磁盘空间也要重点关注。集群模式下 AOF 和 RDB 文件会分散在各个节点上如果某个节点磁盘写满Redis 会拒绝写入进而影响整个集群。给日志和持久化文件单独挂盘并配置磁盘使用率告警这个钱不能省。我自己的习惯是每个季度对集群做一次巡检内容包括cluster info 状态、节点内存增长趋势、大 key 分布、主从复制延迟、持久化文件大小。发现问题早处理比出事之后深夜上线改配置要舒服太多了。