资讯详情

Redis 7集群从零搭建实战:主从迁移到分片故障转移

📅 2026/9/16 1:20:44 | 华诺云谱 👁 阅读
Redis 7集群从零搭建实战:主从迁移到分片故障转移
朋友公司那套Redis要升级集群我在Linux服务器上动手搭建Redis 7集群的整个过程前前后后折腾了几天。原架构是单机Redis加两个只读从库业务一冲高内存就顶到95%主库一宕机从库顶上还得手动改代码里的连接地址。这次我直接把方案落地成了三主三从的Redis 7集群从编译安装到槽位分配从故障切换到扩容缩容做了完整的搭建和验证。如果你也在Linux上准备把Redis从单机或主从模式迁移到集群或者正想学一套能上生产的Redis 7集群搭建过程这篇文章可以直接照着操作里面的坑基本都是我实际踩过之后总结出来的。1. 为什么我坚持用Redis 7集群而不是主从加哨兵1.1 单机与主从模式的真实瓶颈很多人一开始都觉得Redis用单机不就行了数据量少、并发不高的时候确实够但真等业务涨起来单机的三个短板会一起暴露容量有限、吞吐有限、可用性差。主从复制加哨兵Sentinel能解决可用性问题哨兵负责监控主节点状态主节点挂了自动提拔从节点但注意它解决的只是“高可用”并不解决“容量”和“写入吞吐”。主节点内存还是单机上限写请求还是全部打到那个唯一的主节点上从节点只是被动备份如果业务全是写多读少加再多的从节点也分担不了主节点的压力。我见过不少团队用哨兵模式硬撑了好几年因为运维成本确实低。但撑到后期会越来越难受单机内存被业务涨满扩容只能纵向升级把2C8G换成4C16G再换8C32G越换越贵。而且Redis实例一大RDB持久化保存的时间变长故障恢复变慢内存碎片率升高这些隐患都是慢慢浮现的。给Redis做水平扩展、按数据分片存储才是真正能伴随业务成长的架构。1.2 Redis Cluster的槽位分片原理Redis Cluster采用的是无中心化分片设计它把整个键空间切成16384个槽位slot。写入数据时Redis会对key做CRC16校验计算再对16384取模得到一个0到16383之间的编号这个编号对应的槽归哪个主节点管key就写到哪个主节点。集群建好后16384个槽会被分散到各个主节点上每个主节点持有一段槽位区间客户端拿到这一份槽位分配表之后可以直接连到数据所在节点读写。这个设计的妙处在于客户端直连不需要在数据链路里额外插一层Proxy。对比Codis、Twemproxy这类代理方案少了代理这一跳请求延迟更低也少了一个很容易成为瓶颈和单点的组件。代价是客户端必须支持Cluster协议比如Java这边用Jedis Cluster或Lettuce ClusterClient老式的单点连接池要改造。但站在运维角度我更喜欢这种去中心化的架构节点之间通过Gossip协议互相通信没有明显的“管理节点”也就不存在管理节点一挂整个集群瘫痪的问题。1.3 为什么版本选Redis 7Redis 7.0是2022年4月发布的大版本解决了不少历史遗留问题。比如新的AOF格式支持多part重写期间不再瞬间阻塞主线程Redis Function替代了部分Lua脚本场景脚本管理更方便ACL权限模型更完善能做到命令级和key级的权限控制底层内存管理也有优化大key和碎片问题比老版本处理得更好。相比Redis 5或67.x在稳定性、性能、可观测性上都更适合直接上新项目。我本文演示用的是7.0.15这个patch版本实际选择时直接取当时最新的7.0.x或7.2.x稳定版就行7.2、7.4的安装和集群命令流程跟本文完全一致。需要提醒的是Redis 5时代用来搭集群的redis-trib.rb脚本已经废弃了现在所有集群操作都集成在redis-cli里命令统一为redis-cli --cluster所以网上那些三五年前的老教程里很多命令已经不能直接用了。2. 从下载到编译环境准备阶段最容易翻车的几个地方2.1 服务器与目录规划集群最少需要6个节点进程官方推荐3主3从而且主节点最好分散在不同机器上从节点再错开避免一台物理机挂了导致同一分片的主从同时失联。下面按三台Linux服务器来规划这是比较标准的生产落地方式。主机名IP实例端口数据目录日志目录node110.10.10.116379、6380/data/redis/data/6379、/data/redis/data/6380/data/redis/logsnode210.10.10.126379、6380/data/redis/data/6379、/data/redis/data/6380/data/redis/logsnode310.10.10.136379、6380/data/redis/data/6379、/data/redis/data/6380/data/redis/logs如果你手头只有一台虚拟机或者云主机也可以在同一台机器上开6个实例端口从6379排到6384配置过程和命令完全一样只是IP全写成这台机器的内网IP。这种方式拿来学习和测试足够生产环境还是建议拆到多台物理机或云服务器上。为什么要这么较真地分目录因为Redis启动后会把pid文件、日志、持久化文件、cluster配置文件全部写到运行目录里。多个实例如果混在同一个目录nodes-xxx.conf会互相覆盖节点ID错乱集群可能永远组不起来。目录规划好之后创建redis用户并授权sudo useradd -r -s /sbin/nologin redis sudo mkdir -p /data/redis/{data,logs,run,conf} sudo chown -R redis:redis /data/redis2.2 编译依赖与软件安装Redis 7对编译器的要求比老版本高我遇到最典型的情况是在CentOS/RHEL 7系列上编译失败因为系统自带的GCC还停留在4.8.x不支持C11标准需要的新特性。动手之前先检查一下编译环境gcc --version make --versionUbuntu 20.04、22.04默认GCC版本都能顺利编译RHEL 8/9或Rocky Linux自带GCC也足够。如果是RHEL 7需要先用scl或devtoolset把GCC切到高版本不然会在make阶段看到一堆atomic操作找不到、-stdc11参数不被识别之类的报错。装基础依赖用一行命令# RHEL/Rocky/CentOS sudo dnf install -y gcc make wget tar # Ubuntu/Debian sudo apt update sudo apt install -y build-essential wget tar接着用官方源码包编译安装我这里以7.0.15为例cd /usr/local/src wget https://download.redis.io/releases/redis-7.0.15.tar.gz tar xzf redis-7.0.15.tar.gz cd redis-7.0.15 make -j$(nproc) sudo make install PREFIX/usr/local/redis7安装完成后可执行文件在/usr/local/redis7/bin下包括redis-server、redis-cli、redis-check-aof、redis-check-rdb、redis-sentinel。我再做个软链接后面敲命令就不用写全路径了sudo ln -s /usr/local/redis7/bin/* /usr/local/bin/编译过程中如果提示zmalloc.h相关错误基本都是gcc或make缺失补上依赖重走一遍即可。生产环境如果不想从源码编译也可以直接用官方提供的二进制包或Docker镜像但要特别注意架构必须和服务器匹配。2.3 系统内核参数与防火墙放行Redis启动时会在日志里打印几个Warning其中有三个跟内核参数强相关不调它们在低负载下看不出问题并发一上来就很容易出幺蛾子vm.overcommit_memory1允许Redis正常执行后台保存BGSAVE、AOF重写避免fork子进程时因系统内存评估失败被拒绝。net.core.somaxconn默认128建议改成512或更高配合Redis的tcp-backlog配置高并发下客户端连接不容易排队失败。transparent_hugepageTHP默认开启建议改成never。THP在Redis这种频繁内存分配和释放的场景下会造成额外的延迟和内存膨胀。临时调整命令如下想持久化就追加到/etc/sysctl.confecho vm.overcommit_memory 1 /etc/sysctl.conf echo net.core.somaxconn 511 /etc/sysctl.conf echo never /sys/kernel/mm/transparent_hugepage/enabled sysctl -p防火墙端口这块要特别说清楚集群模式下每个实例要开两个TCP端口一个是客户端端口另一个是客户端端口10000的集群总线端口。比如6379对应163796380对应16380。cluster bus专门负责节点间的心跳、槽位同步、故障投票如果只放行了6379却漏了16379创建集群时节点之间会一直互相找不到。Rocky/CentOS上可以用firewall-cmd --permanent --add-port6379/tcp放行Ubuntu用ufw allow 6379/tcp云服务器还要记得在安全组里放行。三台机器之间互相能连通这些端口是后面所有集群操作的前提。3. 三主三从的节点规划与redis.conf关键配置拆解3.1 实例端口与角色分配按照前面第2节的规划三台机器每台跑两个实例最终角色分配在创建集群那一刻自动确定。为了让图文演示清晰我这里给出一个推荐的初始角色分布实例期望初始角色所属机器10.10.10.11:6379master-1node110.10.10.12:6379master-2node210.10.10.13:6379master-3node310.10.10.11:6380slave-1node110.10.10.12:6380slave-2node210.10.10.13:6380slave-3node3但需要注意用--cluster create一条命令拉集群时Redis会自己挑主从并分配槽位不一定严格按这个表来。如果团队有强制的角色规划可以先建3个主节点再分别用cluster replicate给它们挂从节点我在第4节和第6节分别演示这两种做法。3.2 一份可直接落地的redis.conf配置模板以node1的6379实例为例配置文件路径为/data/redis/conf/redis-6379.confport 6379 daemonize yes pidfile /data/redis/run/redis-6379.pid logfile /data/redis/logs/redis-6379.log dir /data/redis/data/6379 bind 10.10.10.11 127.0.0.1 protected-mode yes requirepass RedisCluster_2024 masterauth RedisCluster_2024 appendonly yes appendfsync everysec maxmemory 8gb maxmemory-policy allkeys-lru cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 cluster-require-full-coverage no slowlog-log-slower-than 10000 slowlog-max-len 128其他5个实例只需要改端口、IP、pidfile、logfile、dir、cluster-config-file这几项。这里要特别提醒dir是Redis的工作目录不是配置文件所在目录RDB快照、AOF文件、cluster-config-file都会写到dir指定的路径下。我之前见过有人把dir写成/data/redis/conf结果运行时数据文件全堆在配置目录里非常混乱而且容易在清配置时误删数据。如果生产环境用systemd管理实例就不要daemonize yes让Redis前台运行由systemd负责守护。本文为了演示简洁统一用daemonize方式。3.3 每个配置项为什么这么设理解这些配置项比直接复制一份模板更有用cluster-enabled yes是集群开关只有开启这个Redis进程才会以集群节点身份启动并生成cluster-config-file。cluster-config-file记录当前节点的节点ID、已知的其他节点地址和槽位归属这个文件是运行时自动维护的不要手动改。特别注意每次清空实例重搭集群前必须删除这个文件否则旧节点ID和新槽位信息会让新集群加入失败这个坑我后面会展开讲。cluster-node-timeout设置集群节点判定其他节点下线的最长等待时间故障切换的大致耗时和这个值强相关。15秒是生产常用的折中设太短容易因为JVM GC停顿或网络抖动误判设太长业务恢复太慢。cluster-require-full-coverage这个参数值得好好理解。默认值是yes意思是所有16384个槽都必须有节点负责集群才对客户端提供服务。如果某个分片的主从全部宕机整个集群会拒绝所有读写请求。生产上我更建议设成no即某个分片完全不可用时集群继续服务其他槽位的key只有宕机分片里的key访问报错。这是一种高可用和完整性的取舍必须在业务侧接受“部分key可能暂时不可用”的前提下选择no。requirepass和masterauth是一对必须同时出现的配置。集群环境下节点之间会互相建立主从复制从节点连接主节点时同样要认证所以masterauth必须和requirepass保持一致。如果忘了配masterauth典型现象就是创建集群成功、主节点正常、从节点一直复制不上去日志里反复出现MASTER aborted replication with error: NOAUTH Authentication required。maxmemory和maxmemory-policy决定Redis能用到多少内存以及内存满后怎么淘汰。集群模式下每个主节点和它的从节点保存的是同一份数据所以maxmemory按单个主节点的数据量来估给物理内存留出20%到30%的余量给AOF重写和系统缓冲。淘汰策略方面如果Redis里只放缓存数据可以用allkeys-lru如果缓存和持久化数据混用建议用volatile-lru避免清掉不该清的数据。4. 集群拉起与槽位分配redis-cli --cluster create完整演示4.1 逐个节点启动并检查健康状态先把6份配置文件准备好然后逐个启动。node1上sudo -u redis /usr/local/redis7/bin/redis-server /data/redis/conf/redis-6379.conf sudo -u redis /usr/local/redis7/bin/redis-server /data/redis/conf/redis-6380.confnode2和node3执行同样的命令替换成各自的配置。启动后确认进程和连通性ps -ef | grep redis /usr/local/redis7/bin/redis-cli -h 127.0.0.1 -p 6379 -a RedisCluster_2024 ping返回PONG说明实例正常。再看日志文件正常启动时末尾应该是Ready to accept connections tcp。此刻因为cluster-enabled yes但还没和其他节点组网用cluster info查看会看到集群状态还是初始状态节点列表里只有自己。这个阶段不用急继续往下走。有一点必须提前说清楚集群启用了密码之后后面所有redis-cli的--cluster子命令都要带-a或者提前设置环境变量export REDISCLI_AUTHRedisCluster_2024如果不带认证创建集群时会直接报认证失败新手在这里卡住的情况非常多。4.2 正式创建集群与分片计划解读组网只需要一条命令在任意一个节点机器上执行export REDISCLI_AUTHRedisCluster_2024 /usr/local/redis7/bin/redis-cli --cluster create \ 10.10.10.11:6379 10.10.10.12:6379 10.10.10.13:6379 \ 10.10.10.11:6380 10.10.10.12:6380 10.10.10.13:6380 \ --cluster-replicas 1--cluster-replicas 1意思是每个主节点配1个副本。Redis会尽量把主从拆到不同机器上比如node1:6379的从节点大概率会被安排在node2或node3的6380上而不是放在node1本机。这样做的好处是一台机器宕机时不至于某个分片的主从同时丢失。执行后它会先尝试把所有节点加入集群然后打印一份槽位分派计划类似这样 Performing hash slots allocation on 6 nodes... Master[0] - Slots 0 - 5460 Master[1] - Slots 5461 - 10922 Master[2] - Slots 10923 - 16383 Adding replica 10.10.10.12:6380 to 10.10.10.11:6379 ... Can I set the above configuration? (type yes to accept):看到这个输出说明节点间连通性没问题。确认计划没问题后输入yesRedis会开始批量分配槽位把6个节点串联成完整的集群。完成后出现[OK] All 16384 slots covered集群基本就建成了。如果卡在Waiting for the cluster to join一直不动十有八九是16379这段cluster bus端口没放行或者bind地址配置有问题回头检查防火墙和安全组。这个我在踩坑部分还会再展开。4.3 读写验证与多key操作限制集群建完用cluster模式验证数据路由/usr/local/redis7/bin/redis-cli -c -h 10.10.10.11 -p 6379 -a RedisCluster_2024进入交互后执行set name redis-cluster-demo get name-c是cluster模式客户端会自动把命令路由到key所在节点体感上跟操作单机Redis一样。再查一下集群状态cluster info cluster nodescluster nodes输出的每一行前面是40位节点ID中间是IP和端口flags字段有master或slave标识最后是槽位范围。通过这些输出能清楚看到谁持有哪些槽。集群模式下有个使用限制必须知道不是所有单机命令都可用。比如在一个普通连接里直接执行mget如果多个key的槽位不一致会报CROSSSLOT Keys in request dont hash to the same slot。解决办法是用hash tag把key写成{user:1001}:name和{user:1001}:age这种形式Redis只对{}里的内容计算hash两个key必然落到同一槽mget就能执行了。平时设计key时就要提前规划好这个约束否则后面做批量操作会很痛苦。5. 故障转移实测主节点宕机后集群到底发生了什么5.1 先确定当前的主从分布故障验证前先记录当前拓扑。执行cluster nodes把每个节点的角色和持有槽位范围记下来方便后面对比。假设当前拓扑是10.10.10.11:6379master槽0-546010.10.10.12:6379master槽5461-1092210.10.10.13:6379master槽10923-16383三台机器的6380实例分别作为对应主的从节点注意实际分配未必完全这样以你cluster nodes的输出为准这里只是示意图。5.2 手动杀主进程后的选举过程现在模拟最恶劣的情况直接强杀node1的6379主进程kill -9 24251杀掉之后不要急着看结果稍微等一会儿。大约15秒对应cluster-node-timeout 15000集群会判定主节点失联从节点开始发起选举。选举有硬性条件从节点发现自己复制的主节点已不可达且不可达时间超过cluster-node-timeout同时它需要拿到多数主节点的投票。因为这里是3个主节点从节点至少拿到2票才能晋升。这段时间在node2或node3的主节点日志里能看到类似Start of election delayed、Failover election won的记录。等20秒左右再查看节点列表/usr/local/redis7/bin/redis-cli -h 10.10.10.11 -p 6380 -a RedisCluster_2024 cluster nodes大概率会看到某个6380从节点变成了master槽位还是原来的0-5460只是归属的节点ID变了。整场故障切换过程中只要客户端用的是带集群协议的连接比如Jedis Cluster或Lettuce自动重连后就会刷新节点拓扑不需要人工干预把流量切到新主节点上。这就是Cluster模式比哨兵模式省心的地方故障转移对业务基本透明。5.3 原主恢复后会变成什么角色把之前强杀的进程拉起来sudo -u redis /usr/local/redis7/bin/redis-server /data/redis/conf/redis-6379.conf启动后它带着旧节点ID回到集群发现自己原来负责的槽位已经被其他节点接管于是自动降级为从节点挂到新晋升的主节点下面。再执行cluster nodes确认flags字段会从master变成slave并指向新主的节点ID。这个验证通过说明集群的自愈能力完整。如果后续想让这个实例重新当主可以在它作为从节点时登录该实例执行cluster failover手动触发一次故障转移。它会先把主节点的数据同步对齐再平滑切换对读写影响很小适合在维护窗口里做角色切换。6. 扩容与缩容给集群加节点、腾挪槽位的操作细节6.1 扩容的本质加主节点才扩大容量集群的容量由主节点负责的槽位总数决定所以想扩容加的一定是主节点。加从节点只是提高某一部分数据的冗余度并不增加可用容量。这句话听起来简单但我见过有人为了扩容把一堆热key手动拷到新节点结果发现集群路由根本不会把新key导过去因为槽位分布没变新节点连一个槽都没有。正确姿势是新节点加入集群然后从其他主节点迁一部分槽位给它。迁移完成后新节点才真正承担数据存储和读写。6.2 增加一个主节点并重新分槽假设要新加一台机器node410.10.10.14先在它上面启动一个cluster-enabled的Redis实例然后加入现有集群。执行add-node时要给两个地址前一个是新节点后一个是集群里任意一个老节点老节点作为通信入口export REDISCLI_AUTHRedisCluster_2024 /usr/local/redis7/bin/redis-cli --cluster add-node \ 10.10.10.14:6379 10.10.10.11:6379执行后提示新节点已加入但它当前没有槽位只是一个空主节点。接下来从老节点迁移一部分槽过来/usr/local/redis7/bin/redis-cli --cluster reshard \ 10.10.10.11:6379 \ --cluster-from 10.10.10.11:6379,10.10.10.12:6379,10.10.10.13:6379 \ --cluster-to 新主节点ID \ --cluster-slots 4096 \ --cluster-yes--cluster-from指定从哪些老主节点迁槽--cluster-to写新主节点的ID--cluster-slots是要迁的槽位总数。迁移过程中槽里的key会从源节点逐个搬运到目标节点业务读写对外是透明的但会消耗网络IO高峰期做reshard会影响延迟所以应该安排在低峰期执行。迁移完成后用cluster nodes查看新节点就有了自己的槽位段。reshard过程中有个容易忽略的细节每个槽里的key是挨个迁移的如果某个key特别大迁移时间会非常长期间该key的访问会短暂阻塞。所以日常就要避免超大key尤其在集群环境一个几十MB的大key一旦被迁移整个迁移任务都可能被拖住。6.3 给新主补副本与缩容下线新主如果还需要从节点可以用现成的空实例/usr/local/redis7/bin/redis-cli --cluster add-node \ 10.10.10.14:6380 10.10.10.11:6379 --cluster-slave --cluster-master-id 新主节点ID如果没有现成空实例也可以登录到一个已有的从节点上执行cluster replicate切换复制关系但那样等于改了原节点角色操作前必须想清楚拓扑。缩容是扩容的反向操作核心思路是先把要下线节点上的槽全部迁走再把它从集群里移除。比如下线一个主节点# 1. 把这个主节点的所有槽迁到其他主节点 /usr/local/redis7/bin/redis-cli --cluster reshard \ 10.10.10.11:6379 \ --cluster-from 要下线的主节点ID \ --cluster-to 接收槽位的主节点ID \ --cluster-slots 该主节点槽位总数 \ --cluster-yes # 2. 确认槽位迁空后删除节点 /usr/local/redis7/bin/redis-cli --cluster del-node \ 10.10.10.11:6379 要下线的主节点ID下线从节点更简单不需要迁槽直接del-node即可。无论下线主节点还是从节点操作前都要确认该实例的数据目录里没有需要保留的数据一旦del-node成功它在集群拓扑里的身份就彻底没了。7. 我踩过的坑集群搭建中常见的五个高频问题7.1 nodes.conf残留导致新节点加入失败重搭集群时启动实例后日志里出现大量节点握手失败或者新实例加入集群后查到的槽位信息还是旧的这种情况多半是dir目录下残留了上一次运行的nodes-6379.conf。这个文件里记录了旧节点ID和一堆已经失效的节点地址Redis启动时会信任这些信息导致它一直尝试连接已经销毁的老节点。解决方法是重搭之前把数据目录下的nodes-*.conf、dump.rdb、appendonlydir这些运行时文件一起清理干净再启动实例。我后来习惯了每次测试都写个清理脚本先把目录清掉再启动从根上避免这种问题。7.2 cluster bus端口被防火墙吞掉这是我最开始搭集群时卡得最久的问题。现象是--cluster create执行后一直停在Waiting for the cluster to join或者节点偶尔能互通但马上超时。排查时先在节点之间手动测试16379端口连通性nc -zv 10.10.10.12 16379如果连接不上说明防火墙或安全组没放行cluster bus端口。当时我检查了6379端口忘了还有16379这一层白白耗了半个多小时。记住这个规则只要开了集群模式客户端端口10000的TCP端口必须同步放行。7.3 maxmemory没设置内存打满引发连锁故障集群运行一段时间后某个主节点把物理内存占满触发系统OOM进程被kill随后该分片的从节点晋升为新主。但新主因为继承了同样大的数据量内存也几乎满加上一堆key过期淘汰的CPU开销整个集群响应变慢。这个坑的根源就是没在配置里写maxmemory。每个实例都必须明确设置maxmemory和淘汰策略不要因为物理机内存大就放任Redis使用全部内存。操作系统、AOF重写、后台RDB保存都需要内存缓冲Redis一个人全占了后续的fork和重写操作会非常难受。7.4 requirepass设了但masterauth没配创建集群时主节点一切正常但从节点状态一直异常日志里反复出现MASTER aborted replication with error: NOAUTH Authentication required。原因就是主从复制连接需要认证从节点连主节点时不知道该用什么密码。解决方式很简单把masterauth写成和requirepass相同的值然后重启所有从节点。这个坑在已有密码的单机Redis改造为集群时特别容易出现改配置时只记得加requirepass忘了masterauth是给复制链路用的。7.5 老系统GCC版本过低在CentOS/RHEL 7这类老系统上编译Redis 7make阶段连续报错是家常便饭常见的有cc1: error: unrecognized command line option -stdc11以及atomic相关头文件缺失。检查系统发行版和GCC版本RHEL 7建议用devtoolset-11切GCC或者干脆换到RHEL 8/9、Ubuntu 22.04这类新版本系统。如果不想折腾源码编译直接拉官方二进制包或Docker镜像也是可行方案只要注意CPU架构匹配就行。踩完这些坑之后我给自己定了两个习惯每次搭建前先跑一遍端口连通性检查重搭集群前必清数据目录。这两个习惯帮我省下大量排错时间。集群这类分布式系统的故障往往不是单点原因排错时要优先怀疑网络再怀疑配置残留最后才怀疑Redis本身。毕竟大部分情况下Redis的代码比我们以为的要可靠得多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。