ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Redis三主三从集群搭建实战:从零配置到故障转移验证

Redis三主三从集群搭建实战:从零配置到故障转移验证 1. 为什么是三主三从集群架构背后的取舍逻辑三主三从这四个字是所有学习Redis集群安装的人绕不过去的一道坎。如果你已经搞定了单机版Redis也做过主从复制接下来大概率就会遇到一个现实问题业务数据量一上来单机内存不够用了主从架构虽然能扛读写分离但主节点一挂需要人工介入而且数据量超过单机物理上限时加再多副本也解决不了扩容问题。这时候就需要引入Redis集群——把数据水平切分到多个节点上同时通过多副本保证高可用。这篇文章我会按照自己在一台Linux服务器示例用的是Rocky Linux 9CentOS 7/8流程几乎一致上从零搭建三主三从Redis集群的完整过程把配置、命令、验证步骤和我踩过的坑一次性讲透适合已经会Redis单机安装、准备上集群的运维和开发同学参考。1.1 单机、主从、集群三种模式的分水岭很多人在该不该上集群这个问题上纠结很久。我个人的判断标准很简单只要单台服务器的内存已经快被Redis占满或者你担心某个节点挂了之后整个缓存服务不可用就该考虑上集群了。单机Redis最大的优势就是简单一个进程、一个端口、一套配置文件所有数据都在一个实例里所有命令都能用。但它有两个天花板一是容量天花板你的内存多大Redis就能缓存多少二是可用性天花板进程一崩全站缓存瞬间归零数据库直接被流量打穿。主从复制解决的是可用性不解决容量。Master写、Slave同步你可以做读写分离Master挂了还能手动把Slave提升上来但这个提升通常需要人工干预或者额外搭配哨兵组件。更关键的是主从架构下所有节点存的是同一份数据你加再多Slave总容量还是只有Master那台机器那么大。集群模式跟前面两种有本质区别数据不是冗余存放而是被分片存放在不同节点上。它把一个完整的Key空间切成16384个槽位每个主节点负责其中一部分槽位每份数据又有自己的副本节点。三主三从是我最推荐的入门规模——三个主节点把数据水平切成三份任何一个主节点挂了对应的从节点能在几秒内顶上整体容量是三台服务器的内存总和。1.2 16384个槽位如何在三主之间分配Redis Cluster里判断一个Key该放到哪个节点靠的是CRC16算法对Key做CRC16校验然后对16384取模得到一个0到16383之间的槽位号这个槽位属于哪个主节点Key就落在哪个主节点上。16384这个数字不是随便定的。官方设计时考虑了消息开销和心跳包大小——集群节点之间通过Gossip协议互相交换槽位信息每个节点的心跳包里要携带自己负责的槽位bitmap16384个槽位换算成位图是2KB16384/8能压在合理范围内。三主节点分16384个槽非常接近均分。默认情况下每个主节点大约负责5461个槽其中两个主节点各5461一个主节点5462。如果你三个主节点的内存不一样大可以用reshard手动调整槽位权重给内存大的节点多分一些槽位默认是按节点数均分。理解槽位分配之后很多现象就解释得通了比如集群模式下MGET这种跨Key操作如果多个Key分布在不同的槽位节点上是没法直接执行的需要用Hash Tag把相关Key归到同一个槽位里。这就是集群架构给应用层带来的第一个约束后面接入客户端时还要再提一次。1.3 三主三从的最少节点门槛与仲裁机制为什么官方文档明确写了一个集群最少需要6个节点因为集群模式下每个分片Shard至少要有1主1从否则这个分片上的数据就没有任何冗余。三个分片对应三个主节点三个从节点刚好凑成6个。如果只有三主零从集群也能创建但任何一个主节点挂了它负责的那部分槽位就没人接管了集群会进入fail状态部分Key完全不可用。从这个角度看三主三从是最小的高可用集群规格。集群里的主从关系不是Redis Sentinel那种独立哨兵进程在监控而是节点之间互相通过Gossip协议通信。每个节点每秒会跟其他节点交换PING/PONG消息把节点状态在线、疑似故障、已故障传遍整个集群。当一个主节点被多数主节点标记为疑似故障PFAIL并且超过cluster-node-timeout设定的时间后它会被正式判定为FAIL这时它名下的从节点就会发起选举成为新的主节点。这个机制没有中心节点任何一个节点挂了集群都还能自我修复代价就是故障检测需要几秒到十几秒的延迟具体由cluster-node-timeout控制我习惯设置为15000毫秒。1.4 端口规划从6379到16379安装前先把端口想清楚后面能少踩一半的坑。我见过太多人只在防火墙里放开了6379结果集群一直创建不起来日志里全是节点连接超时最后发现是集群总线端口没放行。Redis Cluster里每个节点实际占用两个TCP端口一个是客户端连接端口client port默认6379用来处理命令请求另一个是集群总线端口cluster bus port默认是客户端端口加10000也就是16379。节点之间所有Gossip通信、槽位迁移、主从握手全部走总线端口。三主三从意味着你有三个客户端端口和三个总线端口。我习惯的规划如下节点角色客户端端口集群总线端口数据目录Master-1637916379/data/redis/6379Slave-1638016380/data/redis/6380Master-2638116381/data/redis/6381Slave-2638216382/data/redis/6382Master-3638316383/data/redis/6383Slave-3638416384/data/redis/6384如果六个节点不在同一台机器上而是在三台或多台机器上则每台机器上要规划好各自的端口。这里顺便提醒一句云服务器控制台的安全组里放行的端口和你Linux服务器上firewalld或iptables放行的端口必须同时保持开放缺一个都不行。2. 环境准备与关键配置编译安装、系统参数、目录规划2.1 编译安装Redis前的系统依赖三主三从集群对操作系统的要求其实比单机版高不了多少。我这里以Rocky Linux 9为例CentOS 7/8、Ubuntu Server的流程大同小异只是包管理器从yum/dnf换成了apt。首先确认系统里有没有编译工具链。Redis是C语言写的从源码编译安装必须有gcc和make。Rocky Linux 9自带的gcc版本足够编译Redis 7.xyum install -y gcc gcc-c make tcltcl是给Redis跑内置测试用的跑make test会用到。如果你不想跑测试可以省略但我建议还是装上编译完跑一遍make test能提前暴露很多潜在问题。然后去Redis官网或国内镜像站下载源码包。我用的版本是redis-7.0.147.x系列在集群功能和稳定性上已经很成熟了。wget https://download.redis.io/releases/redis-7.0.14.tar.gz tar -xzf redis-7.0.14.tar.gz cd redis-7.0.14 make -j$(nproc) make installmake install会把redis-server、redis-cli这些二进制文件装到/usr/local/bin下。装完之后先执行redis-server --version确认版本号再顺手跑一下redis-benchmark --version防止某些发行版提前装了低版本覆盖PATH。2.2 三个必调的系统内核参数Redis官方文档里点名了几个系统参数单机版调不调影响不大但集群版不调运气不好会出诡异问题。第一个是vm.overcommit_memory。Redis做RDB持久化时会fork子进程在写COW写时复制页面时如果系统内存不够又启用了默认的overcommit策略内核可能拒绝分配内存导致fork失败。集群模式下每个节点都可能持续做RDB/AOF操作保险起见设为1sysctl -w vm.overcommit_memory1 echo vm.overcommit_memory 1 /etc/sysctl.conf第二个是关闭Transparent Huge PagesTHP。THP会把小内存页合并成大页本意是减少TLB miss但Redis的AOF重写和RDB fork对内存页的释放时机非常敏感THP反而会导致fork后延迟变高、内存占用暴涨。关闭方式echo never /sys/kernel/mm/transparent_hugepage/enabled这个修改重启后会失效需要写进rc.local或者用systemd unit文件固定。第三个是somaxconn。如果生产环境的并发连接数很高内核backlog太小会导致客户端建连超时。我一般顺手调大sysctl -w net.core.somaxconn1024 sysctl -w net.ipv4.tcp_max_syn_backlog1024这些参数单独拿出来看都不起眼但集群里6个节点同时跑起来之后任何一个因为fork失败或连接超时都可能引发雪崩式的故障迁移提前调好是值得的。2.3 目录与账号规划我最开始搭集群的时候把Redis数据目录放在/usr/local/redis下面结果机器重装数据全没了。后来学乖了统一用/data/redis/端口号的结构每个节点一个独立目录日志、RDB文件、AOF文件、节点配置文件都在各自目录里排查时非常直观。mkdir -p /data/redis/{6379,6380,6381,6382,6383,6384} mkdir -p /data/redis/logs生产环境建议给Redis单独建一个系统账号不要用root跑。注意Redis目录的属主和属组都要改成该账号否则以后AOF重写写临时文件时会遇到权限拒绝useradd -M -s /sbin/nologin redis chown -R redis:redis /data/redis集群模式下每个节点的nodes-6379.conf这类文件也是运行中自动生成的所以目录权限必须放给redis用户。我见过有人图省事用root启动所有节点数据目录权限倒是没问题了但一旦Redis进程被利用整台机器就直接暴露在风险里这个习惯不能养。2.4 防火墙与安全组这里单独拎出来说是因为我至少帮四个人排过明明端口开着为什么集群建不起来的问题。你需要在两个层面同时放行端口Linux本机的firewalld或iptables以及云控制台的安全组。以firewalld为例firewall-cmd --permanent --add-port6379-6384/tcp firewall-cmd --permanent --add-port16379-16384/tcp firewall-cmd --reload关于SELinuxRocky Linux默认是Enforcing模式如果不想折腾建议临时设为Permissive验证一下集群能正常跑再决定要不要给Redis写SELinux策略setenforce 0我不会一刀切说必须彻底禁用SELinux但现实情况是大多数运维同学直接选择关闭它避免大量不明所以的访问拒绝日志。如果你的安全要求严格那可以另写SELinux模块把6379/16379等端口的监听和连接权限赋给redis进程。3. 六个节点的配置文件逐个拆解从单机到集群的关键开关3.1 cluster-enabled是第一个分水岭Redis单机实例和集群节点本质上是同一份二进制只是配置文件里几个开关不同。第一个开关就是cluster-enabledcluster-enabled yes这个参数默认是no。设为yes之后Redis进程启动时会把自己当成集群节点来运行开始监听集群总线端口并且会读取或生成一个集群节点配置文件。这个文件默认叫nodes.conf里面记录着这个节点已知的集群拓扑信息。第二个关键开关是cluster-config-file它指定刚才说的节点配置文件名。每个节点的文件名一定要不同否则多个进程共享同一个nodes.conf可能会出现非常诡异的角色混乱cluster-config-file nodes-6379.conf第三个是cluster-node-timeout单位毫秒表示一个节点多久没响应就会被判定为疑似故障。15秒是比较中庸的值。设得太小网络抖动会导致频繁误判和主从切换设得太大真故障时恢复时间会拖得很长。还有一个很多人忽略的cluster-require-full-coverage参数。默认yes表示集群中只要有任何一个分片的槽位没有主节点负责整个集群就会拒绝客户端请求。在三主三从的正常情况下没问题但在做节点维护、临时取出节点时建议设为no否则业务会有大面积报错cluster-require-full-coverage no3.2 一份模板六份变体下面是一份我实际使用的节点配置文件模板以6379节点为例bind 0.0.0.0 protected-mode yes port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile /data/redis/logs/redis-6379.log dir /data/redis/6379 appendonly yes appendfsync everysec cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 cluster-require-full-coverage no其他五个节点只需要把port、pidfile、logfile、dir、cluster-config-file里的路径和文件名改成对应端口即可。有几个参数值得讲一下。bind 0.0.0.0意味着监听所有网卡。如果你有多块网卡或者云主机有内外网IP更精准的做法是只bind内网IP或者配合cluster-announce-ip/port来声明客户端能访问到的地址。云上环境经常出现一个问题节点之间通过内网通信没问题但客户端从外网连接时拿到的节点导向地址可能是内网IP导致客户端无法跳转。这种情况你必须在每个节点配置里显式设置cluster-announce-ip为公网IP或客户端可达的IP。protected-mode yes配合bind 0.0.0.0是有风险的。Redis的protected-mode逻辑是如果没设置requirepass并且bind了所有网卡那Redis只允许来自回环地址的访问。这样做的结果是集群创建时节点之间通过真实IP通信会被拒。所以我建议要么设置requirepass密码要么在严格隔离的内网环境里把protected-mode设为no。我在演示环境里用的是后者但生产环境必须设密码。生产环境的密码是集群模式里的经典难点所有主从节点必须使用同一个requirepass同时还要设置masterauth否则主从切换后新主节点无法向旧主节点认证requirepass your-strong-password masterauth your-strong-passwordredis-cli创建集群时也要带密码认证参数。这个细节后面会讲。3.3 为什么六个节点建议全部先独立启动配置写完之后不要急着创建集群而是先把六个节点全部以单机模式启动这样能先把配置错误暴露得干干净净。启动方式很简单redis-server /data/redis/6379/redis-6379.conf redis-server /data/redis/6380/redis-6380.conf # ... 依次启动剩余四个节点启动后逐个用redis-cli确认每个节点的状态redis-cli -p 6379 ping redis-cli -p 6379 info server | grep version redis-cli -p 6379 cluster info注意cluster info在未创建集群时会返回cluster_state:fail这是正常的因为节点虽然启动了集群模式但还没有形成集群拓扑。你要确认的是节点进程能稳定运行日志里没有明显报错。我还习惯在这时候验证一次重启。随便挑一个节点kill掉再启动确认它能正常重新加载目录里的AOF/RDB文件这一步能避免后面集群建好后才发现持久化配置有问题导致重启丢数据。4. 集群创建与角色确认用一条命令把六个节点串起来4.1 redis-cli --cluster create的参数细节六个节点都启动确认无异常后终于可以创建集群了。Redis 5.0之后的redis-cli内置了集群管理的完整子命令不需要再额外安装redis-trib.rb也不需要Ruby环境对运维来说友好太多了。创建命令如下redis-cli --cluster create 192.168.1.101:6379 192.168.1.101:6380 \ 192.168.1.101:6381 192.168.1.101:6382 \ 192.168.1.101:6383 192.168.1.101:6384 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点配备1个从节点。redis-cli会读一遍所有节点的信息然后自动生成一个合理的分配方案。这个方案通常满足三个原则槽位尽量均分主节点尽量分散在不同机器上如果节点跨机器从节点尽量不与它的主节点落在同一台机器上。如果配置了requirepass创建命令里需要加认证参数redis-cli -a your-strong-password --no-auth-warning \ --cluster create 192.168.1.101:6379 ... --cluster-replicas 1如果不加你可能会在创建中途看到权限拒绝然后整个创建流程卡在那里。4.2 创建过程输出逐行解读执行创建命令后redis-cli会把六个节点信息打出来然后问你是否接受这个分配方案。第一次跑的人容易被那一段Can I set the above configuration?卡住回车确认就行。确认后你会看到类似这样的输出[OK] All nodes agree about slots configuration. Check for open slots... Check slots coverage... [OK] All 16384 slots covered.这段输出说明槽位已经分配完整没有遗漏没有重叠16384个槽全部有主节点负责。到这步集群就算创建成功了。接下来验证节点角色。用cluster nodes命令redis-cli -p 6379 cluster nodes输出里面每行是一个节点信息包括节点ID、IP:端口、角色标志、负责的槽位范围。比如某种输出0c4f20be... 192.168.1.101:637916379 myself,master - 0 1700000000000 1 connected 0-5460master节点后面跟的connected 0-5460就表示它负责0到5460号槽位。4.3 集群状态验证三板斧第一板斧看cluster info的cluster_state是不是ok。注意每一节点上都可以执行我用的是连接6379再查。如果你之前设了cluster-require-full-coverage yes一旦某个分片的主节点挂了cluster_state会立刻变成fail所有命令都可能被拒绝。所以我一般按上面配置里写的那样把它设成no。第二板斧验证槽位和数据的路由。用redis-cli的-c模式连接任意一个节点写一个Key再读出来redis-cli -c -p 6379 set user:1001 zhangsan redis-cli -c -p 6381 get user:1001-c模式下客户端会自动处理MOVED跳转和ASK跳转所以即使Key落在其他节点上读写也能成功。如果你不带-c去非槽位节点写Key会得到一个MOVED错误这是集群路由的正常行为不是故障。第三板斧验证主从复制。进入任意从节点执行info replication确认role是slavemaster_host和master_port指向正确的主节点master_link_status是upredis-cli -p 6380 -c info replication到这里三主三从集群的基础功能已经全部就绪。接下来是这个项目里最有意思的部分——故障演练。5. 故障演练、扩容思路与高频踩坑实录5.1 手动杀掉主节点观察从节点怎么上位搭建集群的第一件事就是验证高可用不是验证它能缓存数据。我的做法是挑一个主节点直接kill掉观察集群的自动恢复流程。比如杀掉192.168.1.101:6379kill -9 $(cat /var/run/redis_6379.pid)杀之前先记录一下6379的从节点是谁。然后等10到15秒再看cluster nodesredis-cli -p 6381 cluster nodes你会看到原来6379的状态从master变成fail而它对应的从节点从slave变成了master接手了0-5460的槽位。整个过程不需要任何人工干预这就是集群高可用的核心能力。注意这个failover不是瞬时的。Gossip协议需要传播故障信息从节点需要发起选举并获得半数以上主节点的投票整个过程通常十几秒。业务侧如果配置了合理的重试机制这十几秒的抖动可以平滑过去。杀掉的主节点重新启动之后它的表现是找到自己原来所在分片的新主节点自动降级为从节点并开始同步数据redis-server /data/redis/6379/redis-6379.conf redis-cli -p 6381 cluster nodes | grep 6379这里有个容易踩的坑主节点被kill后如果数据源目录里还有旧RDB/AOF数据重新启动时会先把旧数据加载进内存再向新主发送PSYNC进行全量同步。如果旧数据很大重启后的一段时间可能占用较多内存和CPU同步完成前它是不对外提供服务的所以没事别乱kill生产主节点最好走CLUSTER FAILOVER这种优雅切换。5.2 扩容思路增加一组主从的大致路径三主三从只是个起点。业务增长到内存再次吃紧时集群是可以在线扩容的不需要重建。扩容的基本套路是先加节点再迁移槽位。把新节点的配置文件写好、启动好之后用add-node将它加入集群redis-cli --cluster add-node 192.168.1.102:6385 192.168.1.101:6379加进去的节点默认是master但没有任何槽位需要reshard把一部分槽位迁过去redis-cli --cluster reshard 192.168.1.101:6379 --cluster-from 源节点ID --cluster-to 新节点ID --cluster-slots 1000reshard是个异步过程槽位迁移时相关Key会分批次从源节点搬到目标节点期间客户端可能会遇到ASK错误正常处理重试即可不会有数据丢失。缩容就反过来先把槽位移走再用del-node移除节点。完整讲扩容又是一篇文章了这里先把路线图画清楚至少知道三主三从不是终点随时可以往四主四从甚至更多节点扩展。5.3 我踩过的四个高频坑第一个坑nodes.conf文件残留。如果你之前启动过一个Redis节点后来删掉了它的数据目录或者复用了同一个目录里面残留的nodes.conf会让新节点以为它认识一堆节点导致加入集群时报Node is not empty。解决办法很简单搭建环境时用全新目录或者删掉数据目录里所有nodes开头的文件再启动。第二个坑bind 127.0.0.1。这是单机版习惯害的。单机Redis绑定回环地址没问题但集群节点之间需要跨IP通信bind 127.0.0.1会导致节点间握手失败日志里全是Connection refused。集群环境的bind最少要绑内网网卡地址。第三个坑防火墙只放行了业务端口忘了集群总线端口。表现是节点能启动redis-cli --cluster create命令能执行但节点之间一直处于握手状态pong收不到集群卡在创建阶段。解决方式就是前面说的6379-6384和16379-16384两段端口同时放行。第四个坑重复执行create命令。中途失败后直接再执行一次创建命令六个节点里可能有一部分已经处于集群状态这时候会得到各种奇怪的报错。我实测最有效的处理方式是把相关节点进程全部停掉清掉数据目录下的nodes.conf和RDB/AOF文件保留配置文件然后重新启动再创建。测试环境可以这么处理生产环境千万别这么玩最好先在测试环境完整演练一遍再上生产。5.4 集群客户端接入与监控的一些补充经验三主三从建成之后接入层还有几个容易翻车的点。使用Jedis或Lettuce这类客户端时建议开启集群模式不要把连接池里塞一堆普通Redis连接。开启集群模式后客户端能自动感知MOVED和ASK重定向维护节点列表和槽位映射主从切换后也能自动刷新节点信息。推荐在接入层之上再套一层带名字分区规则的算法。比如业务Key大多是形如user:10001、order:20240101这样的模式可以在代码里预留Hash Tag机制把需要一起操作的Key用{user:10001}这种格式归到同一个槽位从而支持MGET这类跨Key操作的原子性。这个细节在集群规模变大后越来越重要。最后提醒一下监控告警。三主三从的Redis集群至少要监控这三个指标cluster_state是否为ok、每个分片主从是否各在线、每个节点内存使用率和Key命中率的趋势。用Prometheus配合redis_exporter可以很方便地采集cluster info和cluster nodes信息脚本定期抓取有节点状态变化立刻触发告警。这些准备看起来麻烦等真正出了事节省的是你半夜爬起来救火的时间。我在实际搭建过程中最大的感受是Redis集群的原理一点都不神秘难点全在配置细节和网络规划上。你只要把端口规划清楚、参数逐个理解透、故障演练提前跑一遍三主三从这套架构就能稳稳地成为业务缓存层最可靠的那块压舱石。
返回列表