news 2026/10/21 6:25:38

Redis Cluster集群搭建与SpringBoot接入实战:从单机到集群的完整避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis Cluster集群搭建与SpringBoot接入实战:从单机到集群的完整避坑指南

1. 为什么单机Redis迟早要换成Cluster集群

1.1 从一次线上告警说起

去年下半年,我负责维护的一个订单系统在晚高峰突然开始大量超时。排查下来发现,Redis单机实例的内存已经逼近物理上限,触发了maxmemory淘汰策略,大量热点key被提前驱逐,缓存命中率从95%直接掉到60%以下,数据库瞬间被打穿。那次事故之后,我花了大概两周时间把缓存层从单机主从切换到了Redis Cluster集群版。

这个经历让我意识到一个问题:很多团队在项目初期用单机Redis跑得好好的,业务量一上来就出问题。单机Redis的瓶颈其实很明显——内存受限于单机物理内存,写入吞吐受限于单线程模型,主从架构虽然能读扩展但写仍然是单点。Redis Cluster就是官方给出的分布式解决方案,把数据分片到多个节点上,每个节点只存一部分数据,同时每个分片还有从节点做高可用。

这篇文章我会把Redis Cluster集群版的搭建过程、SpringBoot项目中的接入方式、以及我在实践中踩过的坑,完整地梳理一遍。适合已经用过单机Redis、想往集群方向升级的开发者,也适合正在做技术选型、想了解Cluster实际落地成本的团队参考。

1.2 Cluster到底解决了什么问题

先明确Cluster的核心能力。它把整个key空间划分成16384个哈希槽(hash slot),每个主节点负责一部分槽位。当你写入一个key时,Redis会对key做CRC16计算,然后对16384取模,得到这个key属于哪个槽,再路由到负责该槽的节点上。

这样做的好处有三个:第一,数据分散到多个节点,总内存容量等于所有主节点内存之和,突破了单机上限;第二,写入请求分散到多个主节点,吞吐量随节点数线性增长;第三,每个主节点可以挂从节点,主节点故障时从节点自动晋升,保证可用性。

但Cluster也有代价。它不支持跨槽的多key操作,事务和Lua脚本涉及的key必须在同一个槽里。客户端需要支持集群拓扑发现和请求重定向。运维复杂度比单机高不少。这些限制在选型时都要考虑清楚。

提示:如果你的业务QPS在几万以内、数据量在10GB以内,单机主从加哨兵其实够用了。Cluster更适合数据量大、写入吞吐要求高的场景,不要为了用而用。

2. 集群搭建前的规划与准备

2.1 节点规划与端口分配

Redis Cluster最少需要3个主节点,官方推荐至少3主3从共6个节点。我这次搭建用的是6台独立的服务器,每台跑一个Redis实例,3主3从交叉分布。如果资源有限,也可以在一台机器上用不同端口跑多个实例,但生产环境不建议这么做,一台机器挂了会影响多个节点。

端口规划上,每个Redis实例需要两个端口:一个是客户端通信端口(比如7001),另一个是集群总线端口(客户端端口+10000,即17001),用于节点间 gossip 通信。防火墙必须同时放行这两个端口,很多人搭集群失败就是忘了开总线端口。

节点角色IP客户端端口总线端口
主节点1192.168.1.101700117001
主节点2192.168.1.102700217002
主节点3192.168.1.103700317003
从节点1192.168.1.104700417004
从节点2192.168.1.105700517005
从节点3192.168.1.106700617006

2.2 配置文件的关键参数

每个节点的redis.conf需要重点改这几个参数。cluster-enabled yes 开启集群模式,cluster-config-file nodes-7001.conf 指定集群配置文件(这个文件由Redis自动维护,不要手动改),cluster-node-timeout 15000 设置节点超时时间,单位毫秒。

appendonly yes 建议开启AOF持久化,集群环境下数据安全性更重要。protected-mode no 在受信任的内网环境可以关闭保护模式,否则跨机访问会被拒绝。bind 0.0.0.0 监听所有网卡,同样仅限内网。

还有一个容易忽略的参数:cluster-require-full-coverage。默认是yes,意思是只要有一个槽位不可用,整个集群就拒绝服务。生产环境建议改成no,这样部分节点故障时,其他槽位的数据仍然可读写,可用性更高。

# 以7001节点为例的配置片段 port 7001 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 appendonly yes protected-mode no bind 0.0.0.0 cluster-require-full-coverage no daemonize yes logfile /var/log/redis/redis-7001.log

2.3 系统层面的调优

在启动Redis之前,有几个系统参数需要调整。vm.overcommit_memory 设置为1,避免后台保存时fork失败。net.core.somaxconn 调大到1024以上,应对高并发连接。还有透明大页(THP)建议关闭,否则可能导致Redis延迟抖动。

echo 1 > /proc/sys/vm/overcommit_memory echo 1024 > /proc/sys/net/core/somaxconn echo never > /sys/kernel/mm/transparent_hugepage/enabled

这些参数最好写进 /etc/sysctl.conf 和开机脚本,否则重启后就失效了。我吃过这个亏,有次服务器维护重启后忘了重新设置,Redis延迟毛刺明显增多,查了半天才想起来是THP又开了。

3. 集群创建与验证实操

3.1 用redis-cli创建集群

6个节点都启动好之后,用redis-cli的--cluster create命令一键建集群。--cluster-replicas 1 表示每个主节点配一个从节点,Redis会自动分配主从关系和槽位。

redis-cli --cluster create \ 192.168.1.101:7001 \ 192.168.1.102:7002 \ 192.168.1.103:7003 \ 192.168.1.104:7004 \ 192.168.1.105:7005 \ 192.168.1.106:7006 \ --cluster-replicas 1

执行后会显示一个槽位分配方案,让你确认。输入yes后,集群开始创建,节点之间握手、分配槽位、建立主从关系。看到 "All 16384 slots covered" 就说明集群创建成功了。

这里有个细节:Redis分配主从不是简单的前三个主后三个从,它会打乱顺序。你可以在创建完成后用 cluster nodes 命令查看实际的主从对应关系。如果对主从分布有要求(比如希望从节点和主节点不在同一机架),需要手动用 cluster replicate 命令调整。

3.2 集群状态检查

创建完成后,必须做几项验证。第一,用 redis-cli -c -p 7001 cluster info 查看集群状态,cluster_state 应该是ok,cluster_slots_assigned 应该是16384。第二,用 cluster nodes 查看每个节点的角色和槽位范围。第三,实际写入几个key,验证读写和重定向是否正常。

# 查看集群信息 redis-cli -c -p 7001 cluster info # 查看节点列表 redis-cli -c -p 7001 cluster nodes # 写入测试,-c参数开启集群模式自动重定向 redis-cli -c -p 7001 set test_key hello redis-cli -c -p 7001 get test_key

如果 set 的时候看到 "MOVED 12345 192.168.1.102:7002" 这样的提示,说明客户端没有开启集群模式。加上 -c 参数后,redis-cli会自动跟随重定向,你就能正常读写了。

3.3 槽位迁移与扩容演练

集群跑起来之后,扩容是迟早的事。假设要加一个新主节点7007,步骤是:先启动新节点,然后用 redis-cli --cluster add-node 把新节点加入集群(此时它没有槽位),再用 --cluster reshard 从现有节点迁移一部分槽位给它。

# 添加新节点 redis-cli --cluster add-node 192.168.1.107:7007 192.168.1.101:7001 # 迁移槽位,交互式输入迁移数量和目标节点ID redis-cli --cluster reshard 192.168.1.101:7001

reshard过程会逐个key迁移,如果槽位里数据量大,耗时会比较长。建议在业务低峰期操作,并且迁移前做好数据备份。我一般会先用 --cluster rebalance 做一次均衡检查,看看各节点槽位分布是否均匀。

注意:槽位迁移过程中,被迁移的槽位会短暂处于MIGRATING/IMPORTING状态,客户端访问这些槽位的key时可能收到ASK重定向。成熟的客户端(如Lettuce)会自动处理ASK,但如果你用的是老版本Jedis,需要确认版本是否支持。

4. SpringBoot接入Cluster的完整方案

4.1 客户端选型:Lettuce还是Jedis

SpringBoot从2.x开始默认用Lettuce作为Redis客户端。Lettuce基于Netty,是异步非阻塞的,多个线程可以共享同一个连接,在集群环境下表现更好。Jedis是同步阻塞的,每个线程需要独立连接,连接池开销大。所以新项目我强烈建议用Lettuce。

不过Lettuce有个坑:它默认只在启动时刷新一次集群拓扑,如果集群发生主从切换或槽位迁移,拓扑信息可能过期。需要配置一个定时刷新策略,或者开启自适应刷新。这个后面会详细说。

4.2 依赖引入与配置

pom.xml里引入spring-boot-starter-data-redis,它会自动带入Lettuce。如果需要连接池,还要加commons-pool2。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

application.yml里配置集群节点列表。注意集群模式下,spring.redis.cluster.nodes 列出所有主从节点,客户端会自动发现拓扑。

spring: redis: cluster: nodes: - 192.168.1.101:7001 - 192.168.1.102:7002 - 192.168.1.103:7003 - 192.168.1.104:7004 - 192.168.1.105:7005 - 192.168.1.106:7006 max-redirects: 3 timeout: 3000 lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 2000

max-redirects 是跟随重定向的最大次数,默认就是3,一般够用。timeout 是命令超时时间,集群环境下建议设短一点,比如3秒,避免慢节点拖垮整个请求。

4.3 自定义Lettuce集群拓扑刷新

前面提到Lettuce的拓扑刷新问题,这是接入Cluster最容易踩的坑。默认情况下,Lettuce只在建立连接时获取一次拓扑,之后如果集群扩容或主从切换,客户端可能还在往旧节点发请求,导致连接失败。

解决办法是配置 ClusterTopologyRefreshOptions,开启周期刷新和自适应刷新。周期刷新是每隔一段时间主动拉取拓扑,自适应刷新是在遇到重定向时触发拓扑更新。

@Configuration public class RedisClusterConfig { @Bean public LettuceClientConfigurationBuilderCustomizer lettuceCustomizer() { return builder -> { ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(Duration.ofSeconds(30)) .enableAllAdaptiveRefreshTriggers() .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10)) .build(); ClusterClientOptions clientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(refreshOptions) .maxRedirects(3) .build(); builder.clientOptions(clientOptions); }; } }

这段配置我实测下来很稳。enableAllAdaptiveRefreshTriggers 会在MOVED、ASK、连接断开等情况下触发拓扑刷新,配合30秒的周期刷新,基本能保证客户端拓扑和集群实际状态一致。

4.4 RedisTemplate的序列化配置

默认的RedisTemplate用JDK序列化,存进去的key和value都是二进制乱码,用redis-cli看根本认不出来。生产环境一定要改成String序列化key、JSON序列化value。

@Bean public RedisTemplate<String, Object> redisTemplate( RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; }

用GenericJackson2JsonRedisSerializer有个好处,它会在JSON里带上类信息,反序列化时能自动还原成原对象。但要注意,如果对象结构变了(比如加了字段),旧数据反序列化可能报错。对稳定性要求高的场景,可以用Jackson2JsonRedisSerializer指定具体类型,牺牲一点通用性换稳定性。

5. 集群环境下的开发避坑指南

5.1 跨槽操作的三种解法

Cluster最让人头疼的限制就是跨槽操作。比如你想用 mget 一次取多个key,如果这些key不在同一个槽,就会报 CROSSSLOT 错误。同样,涉及多个key的Lua脚本、事务(MULTI/EXEC)也受此限制。

解法一:用hash tag强制同槽。Redis规定,如果key里包含 {},只有 {} 内的内容参与槽位计算。所以 user:{1001}:name 和 user:{1001}:age 会落在同一个槽。设计key的时候,把需要一起操作的key用相同的tag包起来。

解法二:拆分操作。mget拆成多次get,用pipeline批量发送减少网络往返。事务拆成独立的命令,用分布式锁保证一致性。

解法三:客户端聚合。在应用层把多个key按槽分组,分别请求不同节点,然后合并结果。Spring Data Redis的ClusterOperations接口提供了这种能力,但用起来比较繁琐。

操作类型是否支持跨槽推荐解法
mget/mset不支持hash tag或拆分+pipeline
Lua脚本不支持hash tag保证同槽
事务MULTI不支持分布式锁替代
pipeline支持(分节点)客户端自动分组

5.2 批量操作的性能优化

集群环境下做批量写入,如果一个个set,网络往返次数太多。用pipeline能把多个命令打包发送,大幅减少RTT。但要注意,pipeline里的key如果跨槽,Lettuce会自动按节点分组,分别发送pipeline,这个行为是透明的,不用手动处理。

public void batchSet(Map<String, String> data) { redisTemplate.executePipelined((RedisCallback<Object>) connection -> { StringRedisConnection strConn = (StringRedisConnection) connection; data.forEach(strConn::set); return null; }); }

实测下来,1000个key的批量写入,用pipeline比逐个set快5到8倍。但pipeline不是越大越好,一次打包太多命令会占用大量内存,建议每批控制在500到1000个命令。

5.3 主从切换时的连接处理

集群主节点故障时,从节点会在 cluster-node-timeout 之后发起选举,晋升为主节点。这个过程通常需要10到15秒。在这期间,客户端访问故障节点的请求会失败。

Lettuce的重试机制可以缓解这个问题。配置 spring.redis.lettuce.cluster.refresh 相关参数,配合命令重试,能在主从切换后自动恢复。但要注意,重试只对幂等操作安全,像 incr 这种非幂等命令,重试可能导致重复累加。

我的做法是:对读操作开启重试,对写操作根据业务判断。如果业务能容忍重复写(比如set覆盖),可以重试;如果是计数器类操作,要么用分布式锁,要么在应用层做补偿。

提示:cluster-node-timeout 不要设太小,否则网络抖动会误判节点故障,引发不必要的切换。生产环境建议15秒左右,具体根据网络质量调整。

6. 监控、运维与常见故障排查

6.1 必须监控的核心指标

集群跑起来不是终点,持续监控才能提前发现问题。我重点关注这几类指标:集群状态(cluster_state是否为ok)、槽位覆盖情况(是否有槽位未分配)、节点连接数、内存使用率、命令延迟、主从复制延迟。

用 redis-cli --cluster check 可以快速做一次健康检查,它会检查槽位覆盖、主从关系、各节点状态。建议做成定时任务,每天跑一次,结果异常时告警。

redis-cli --cluster check 192.168.1.101:7001

输出会列出每个主节点的槽位范围、从节点数量、是否有孤立节点。如果看到 "Not all slots are covered" 或者某个节点状态是fail,就要立即处理。

6.2 常见故障速查表

故障现象可能原因排查方法解决方案
集群状态fail槽位未全覆盖cluster info查看slots_assigned用cluster addslots补全
写入报CLUSTERDOWN节点故障未恢复cluster nodes看节点状态修复故障节点或手动failover
频繁MOVED重定向客户端未开集群模式检查客户端配置开启集群模式或更新拓扑
主从切换后连接失败拓扑未刷新查看客户端拓扑缓存配置拓扑刷新策略
内存告警数据倾斜各节点info memory对比检查热点key,重新分片
延迟毛刺大key或慢查询slowlog get排查拆分大key,优化命令

6.3 数据倾斜的处理

集群最容易出现的问题之一是数据倾斜——某些节点内存用得特别多,其他节点很空。原因通常是热点key集中在某个槽,或者key的分布本身不均匀。

排查方法是逐个节点执行 info memory,对比 used_memory 的差异。如果差异超过30%,就要考虑重新分片。用 redis-cli --cluster rebalance 可以自动均衡槽位,但它只迁移槽,不解决热点key问题。热点key还得从业务层面拆分,比如把一个热门商品的缓存拆成多个子key分散到不同槽。

我遇到过一次数据倾斜,原因是某个业务用了自增ID作为key前缀,而自增ID的高位变化慢,导致大量key落在相邻槽位。后来改成对ID做哈希后再拼key,分布就均匀了。这个经验告诉我,key的设计要提前考虑槽位分布,别等出问题再改。

6.4 备份与恢复策略

集群的备份比单机复杂,因为数据分散在多个节点。我用的方案是:每个主节点定期执行 BGSAVE 生成RDB文件,然后把RDB集中备份到对象存储。恢复时,先按原拓扑启动节点,把RDB放回对应目录,再重建集群。

要注意,集群模式下不能用单机的 DEBUG RELOAD 来加载RDB,必须让节点以集群模式启动,然后通过 cluster meet 重新组网。如果拓扑变了(比如节点数不同),还需要重新分片。所以备份时最好记录下当前的槽位分配和主从关系,恢复时照着来。

# 在每个主节点执行 redis-cli -p 7001 bgsave # 备份RDB文件 cp /var/lib/redis/7001/dump.rdb /backup/redis/7001-$(date +%Y%m%d).rdb

备份频率根据数据重要性和可容忍的丢失量来定。对订单类数据,我一般每小时备份一次,RPO控制在一小时内。对缓存类数据,丢了可以从数据库重建,备份频率可以低一些。

7. 一些个人实践体会

Cluster集群搭建本身不难,难的是后续的运维和业务适配。我最大的体会是:上集群之前,一定要先梳理清楚业务里哪些操作是跨槽的,提前设计好key的命名规范。等业务跑起来再改key结构,迁移成本非常高。

另外,客户端版本很关键。Lettuce 5.3以上对集群的支持才比较完善,老版本在拓扑刷新和重定向处理上有bug。SpringBoot 2.3以上默认带的Lettuce版本基本没问题,如果用的是更早的版本,建议手动升级Lettuce。

最后分享一个小技巧:本地开发环境可以用Docker Compose一键起6个Redis节点,省去手动配置的麻烦。把集群搭建脚本化,每次重建环境只要跑一个脚本,几分钟就能搞定。这样测试集群相关功能时,随时可以推倒重来,不用担心把环境搞坏。

# docker-compose.yml 片段,起6个节点 version: '3' services: redis-7001: image: redis:7.0 command: redis-server --port 7001 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes ports: - "7001:7001" - "17001:17001" # 其余5个节点类似,改端口即可

用Docker起集群时,网络模式建议用host,否则集群总线通信可能因为NAT出问题。如果必须用bridge,要确保容器间能通过配置的IP互相访问,cluster announce ip 要设成宿主机IP。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 6:25:06

美赛建模可复现工作流:PDF解析到多目标优化的端到端Python框架

简介&#xff1a;本资源为2022年美国大学生数学建模竞赛&#xff08;MCM/ICM&#xff09;C题——比特币交易策略的F奖获奖项目完整复现包&#xff0c;面向数学建模初学者、金融数据分析爱好者及机器学习实践者。项目深度融合小波降噪、GRU时序预测与动态规划优化&#xff0c;构…

作者头像 李华
网站建设 2026/10/21 6:25:06

以太网VLAN划分实战:基于接口与MAC地址配置详解

1. 实验背景与整体设计思路1.1 为什么以太网基础和VLAN是网络入门的必修课刚接触网络设备配置的朋友&#xff0c;十有八九第一个卡住的地方就是VLAN。不是说它概念有多难&#xff0c;而是很多人一上来就背命令&#xff0c;背完了也不知道自己到底在干什么。我在带新人的时候经常…

作者头像 李华
网站建设 2026/10/21 6:25:05

VLAN划分实验:基于接口与MAC地址的配置对比与排错指南

1. 为什么这个实验值得每个网络初学者反复做三遍刚接触网络设备配置的朋友&#xff0c;十有八九第一个卡住的地方就是VLAN。书上的定义背得滚瓜烂熟——"虚拟局域网&#xff0c;用来隔离广播域"——可真到了模拟器里&#xff0c;面对一台交换机和几台PC&#xff0c;却…

作者头像 李华
网站建设 2026/10/21 6:24:26

企业微信H5授权登录全攻略:从OAuth流程到access_token缓存实战

1. 认清本质&#xff1a;企业微信H5授权到底在干嘛很多初接触企业微信H5开发的童鞋&#xff0c;一上来就盯着那些API文档和参数名死磕&#xff0c;结果把企业微信授权登录完全理解成了传统的账号密码登录。这玩意儿跟微信公众平台的网页授权是一脉相承的思路&#xff0c;但又多…

作者头像 李华
网站建设 2026/10/21 6:24:25

告别技能囤积:把技能当项目管理,搭建你的个人技能树

先讲一个我最近真实的感受。前阵子整理书柜和电脑文件夹&#xff0c;翻出了几十个吃灰的学习资料包——有半年前收藏的某某框架教程&#xff0c;有一年前下载的副业课程&#xff0c;还有更早记了一堆笔记的公开课列表。那一刻我忽然意识到&#xff0c;大多数人所谓的“技能积累…

作者头像 李华
网站建设 2026/10/21 6:23:59

RDD算子深度解析:从原理到性能优化的实战指南

1. 为什么RDD算子值得系统梳理一遍刚接触分布式计算那会儿&#xff0c;我对RDD算子的理解就是“一堆能对集合做操作的函数”&#xff0c;直到真正在生产环境里处理TB级数据&#xff0c;才发现算子选错一个&#xff0c;整个作业的运行时间能从十分钟变成两小时。RDD算子是分布式…

作者头像 李华