作为资深Java工程师,回答Redis集群方案相关问题时,建议采用STAR模型(背景、任务、行动、结果)结构化阐述,并结合技术细节与最佳实践。以下是参考回答框架:
一、项目背景与方案选型(Situation)
背景:
在我负责的电商交易系统中,Redis作为核心缓存中间件支撑高并发读请求(峰值QPS 30万+),存储商品信息、订单状态等高频访问数据。
选型考量:
- 数据量与扩展性:预计未来1年数据规模增长5倍,需支持动态扩容
- 高可用性:系统SLA要求99.99%,需避免单点故障
- 运维复杂度:团队规模有限,需简化集群管理成本
最终方案:
采用Redis Cluster(3主3从) + Spring Data Redis + Lettuce客户端方案,架构图如下:
┌───────────────────────────────────────────────────┐
│ 负载均衡器 │
└───────────────────┬───────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│主节点1│ │主节点2│ │主节点3│
│(7000) │ │(7001) │ │(7002) │
└──┬────┘ └──┬────┘ └──┬────┘
│ │ │
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│从节点1 │ │从节点2 │ │从节点3 │
│(7003) │ │(7004) │ │(7005) │
└─────────┘ └─────────┘ └─────────┘
二、遇到的问题与解决方案(Task & Action)
问题1:集群脑裂导致数据丢失
现象:
某次机房网络闪断后,部分客户端与主节点通信中断,这些客户端将请求发到了从节点(此时从节点已被选举为新主)。网络恢复后,原主节点作为从节点重新加入集群时,其数据被新主覆盖,导致部分写操作丢失。
根因:
Redis Cluster在网络分区时可能产生双主(脑裂),原主恢复后会丢弃自身数据以同步新主。
解决方案:
- 参数优化:
# redis.conf min-replicas-to-write 1 # 至少1个从节点才能写 min-replicas-max-lag 10 # 从节点延迟超过10秒则拒绝写 - 客户端增强:
在Lettuce配置中增加重试策略,优先使用读写分离模式:ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(Duration.ofSeconds(30)) // 定期刷新拓扑 .enableAllAdaptiveRefreshTriggers() // 自适应刷新 .build(); ClusterClientOptions clientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(refreshOptions) .build(); - 业务补偿:
在关键写操作后记录操作日志,通过定时任务比对Redis与DB数据一致性,发现丢失时触发补偿机制。
效果:
脑裂后数据丢失率从0.3%降至低于0.001%,满足业务SLA要求。
问题2:大Key导致集群性能波动
现象:
每日促销活动期间,部分Redis节点CPU使用率飙升至90%+,集群响应延迟从平均5ms增至50ms。
根因:
商品详情页缓存将整个页面数据序列化为单个JSON对象(最大2MB),导致:
- 单节点网络带宽压力大
- 大Key删除时产生长时间阻塞
解决方案:
- 数据拆分:
将商品信息按维度拆分(基础信息、价格、库存等),分散存储:// 原大Key:"product:detail:123" -> 2MB JSON // 拆分为: redisTemplate.opsForValue().set("product:base:123", productBaseInfo); // 20KB redisTemplate.opsForValue().set("product:price:123", productPrice); // 1KB redisTemplate.opsForValue().set("product:stock:123", productStock); // 0.5KB - 异步删除:
使用UNLINK替代DEL命令删除大Key:redisTemplate.execute((RedisCallback<Long>) connection -> connection.unlink("product:history:123".getBytes()) ); - 监控预警:
集成RedisSlowLog监控,设置大Key阈值(如value > 100KB):@Scheduled(fixedRate = 60000) public void monitorBigKeys() { List<SlowLogEntry> slowLogs = redisTemplate.execute((RedisCallback<List<SlowLogEntry>>) connection -> connection.getSlowLog()); // 分析慢日志,识别大Key操作 }
效果:
集群高峰CPU使用率降至60%,响应延迟稳定在5ms以内,吞吐量提升30%。
问题3:缓存雪崩引发DB压力激增
现象:
某秒杀活动开始前,大量商品缓存同时失效,导致瞬时30万QPS请求直接打到DB,数据库连接池耗尽。
根因:
- 缓存Key设置了相同过期时间
- 未对热点Key做特殊保护
解决方案:
- 随机过期时间:
// 设置缓存时添加随机偏移量(5-10分钟) long expireTime = 3600 + ThreadLocalRandom.current().nextInt(300, 600); redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS); - 热点Key永不过期:
对核心商品ID(如Top 1000)使用后台线程定时刷新:@Async public void refreshHotKey(String key) { Product product = productService.getProductFromDB(key); redisTemplate.opsForValue().set(key, product); } - 多级缓存架构:
在Redis前增加本地缓存(Caffeine):LoadingCache<String, Product> localCache = Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(1, TimeUnit.MINUTES) .build(key -> getFromRedis(key));
效果:
缓存失效时DB请求量从30万QPS降至5万QPS,系统稳定性显著提升。
三、最终成果与经验总结(Result)
-
系统指标:
- QPS提升40%,响应延迟降低65%
- 集群可用性达到99.995%,全年故障时间<4小时
- 运维成本降低50%,自动扩容功能支持业务快速增长
-
经验总结:
- 提前规划:根据业务特性设计合理的数据分片策略,避免后期大规模数据迁移
- 分层防御:从客户端、中间件到DB构建多级保护,防止级联故障
- 监控先行:建立完善的监控体系(性能、大Key、慢查询),实现故障预警
-
最佳实践:
- 对写入频繁的场景禁用AOF或使用
everysec策略 - 避免在集群中使用Lua脚本(可能导致全集群阻塞)
- 定期执行
redis-cli --cluster check检查集群健康状态
- 对写入频繁的场景禁用AOF或使用
通过以上结构化回答,既能展示技术深度(如脑裂原理与解决方案),又能体现工程能力(从问题定位到系统优化的完整闭环),同时凸显团队协作与业务价值,符合资深工程师的能力要求。
1298

被折叠的 条评论
为什么被折叠?



