返利软件的分布式缓存架构:Redis集群在高并发场景下的优化策略
返利软件的分布式缓存架构:Redis集群在高并发场景下的优化策略
大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!
在返利软件的业务场景中,商品列表查询、用户返利余额展示、热门活动数据等高频操作,若直接依赖数据库查询,会导致数据库压力剧增,甚至引发服务雪崩。基于此,我们采用Redis集群作为分布式缓存,通过“主从复制+哨兵+分片”架构,结合缓存预热、穿透防护、一致性保障等优化策略,将核心接口响应时间从300ms降至20ms以内,数据库查询压力减少70%。以下从集群架构设计、核心优化策略、代码实现三方面展开,附完整技术方案与代码示例。
一、返利软件Redis集群架构设计
1.1 集群拓扑结构
针对返利软件的高并发需求(如大促期间商品查询QPS达5000+),设计三层Redis集群架构:
- 数据分片层:采用Redis Cluster分片机制,将缓存数据按哈希槽(16384个)分布到3个主节点,每个主节点对应2个从节点,实现数据分布式存储与负载均衡;
- 高可用层:通过Redis Sentinel哨兵集群(3个哨兵节点)监控主节点状态,主节点故障时自动将从节点晋升为主节点,保障服务不中断;
- 客户端层:使用Spring Data Redis结合Redisson客户端,实现集群节点发现、故障自动重连与分布式锁功能。
1.2 核心业务缓存分类
根据返利软件的业务特性,将缓存分为三类,对应不同的过期策略与存储结构:
- 高频读缓存:商品列表、活动规则(采用String/Hash结构,过期时间1小时,定期预热);
- 实时性缓存:用户返利余额、订单状态(采用String结构,过期时间5分钟,更新时主动刷新);
- 分布式锁缓存:库存扣减、并发下单(采用Redisson的RLock,自动释放锁机制)。
二、Redis集群核心优化策略与代码实现
2.1 缓存穿透防护:布隆过滤器+空值缓存
针对“查询不存在的商品ID”等穿透场景,通过布隆过滤器拦截无效请求,结合空值缓存避免重复穿透,代码如下:
package cn.juwatech.rebate.cache.guard;import cn.juwatech.rebate.service.ProductService;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** 缓存穿透防护组件(布隆过滤器+空值缓存)*/
@Component
public class CachePenetrationGuard {// 布隆过滤器:预计存储100万商品ID,误判率0.01private BloomFilter<String> productBloomFilter;// 空值缓存过期时间(5分钟)private static final long NULL_CACHE_TTL = 300;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate ProductService productService;// 项目启动时初始化布隆过滤器(加载所有有效商品ID)@PostConstructpublic void initBloomFilter() {// 1. 从数据库查询所有有效商品IDList<String> validProductIds = productService.listAllValidProductIds();// 2. 初始化布隆过滤器productBloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),validProductIds.size(),0.01);// 3. 将商品ID加入布隆过滤器validProductIds.forEach(productId -> productBloomFilter.put(productId));}/*** 检查商品ID是否可能存在(拦截无效请求)*/public boolean mightContainProductId(String productId) {return productBloomFilter.mightContain(productId);}/*** 缓存空值(避免重复穿透)*/public void cacheNullValue(String key) {redisTemplate.opsForValue().set(key, "", NULL_CACHE_TTL, TimeUnit.SECONDS);}
}
2.2 缓存击穿防护:互斥锁+热点数据永不过期
针对“热点商品缓存过期瞬间大量请求穿透到数据库”的场景,通过Redis分布式锁保证同一时间只有一个请求更新缓存,代码如下:
package cn.juwatech.rebate.cache.service;import cn.juwatech.rebate.cache.guard.CachePenetrationGuard;
import cn.juwatech.rebate.dto.ProductDTO;
import cn.juwatech.rebate.service.ProductService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import java.util.concurrent.TimeUnit;/*** 商品缓存服务(含击穿防护)*/
@Service
public class ProductCacheService {private static final String CACHE_KEY_PRODUCT = "rebate:product:%s";private static final String LOCK_KEY_PRODUCT = "rebate:lock:product:%s";// 普通商品缓存过期时间(1小时)private static final long CACHE_TTL = 3600;// 热点商品缓存过期时间(24小时,配合后台定时刷新实现“永不过期”)private static final long HOT_CACHE_TTL = 86400;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate ProductService productService;@Autowiredprivate CachePenetrationGuard penetrationGuard;/*** 获取商品缓存(含穿透、击穿防护)*/public ProductDTO getProductCache(String productId) {String cacheKey = String.format(CACHE_KEY_PRODUCT, productId);String jsonData;// 1. 穿透防护:布隆过滤器拦截无效商品IDif (!penetrationGuard.mightContainProductId(productId)) {penetrationGuard.cacheNullValue(cacheKey);return null;}// 2. 查询缓存jsonData = redisTemplate.opsForValue().get(cacheKey);if (jsonData != null && !"".equals(jsonData)) {return JSON.parseObject(jsonData, ProductDTO.class);}// 3. 击穿防护:获取分布式锁,确保同一时间只有一个请求更新缓存RLock lock = redissonClient.getLock(String.format(LOCK_KEY_PRODUCT, productId));try {// 尝试获取锁(等待1秒,持有5秒)if (lock.tryLock(1, 5, TimeUnit.SECONDS)) {// 4. 再次查询缓存(避免锁等待期间其他请求已更新缓存)jsonData = redisTemplate.opsForValue().get(cacheKey);if (jsonData != null && !"".equals(jsonData)) {return JSON.parseObject(jsonData, ProductDTO.class);}// 5. 缓存未命中,查询数据库ProductDTO product = productService.getProductById(productId);if (product == null) {// 空值缓存,避免重复穿透penetrationGuard.cacheNullValue(cacheKey);return null;}// 6. 存入缓存(热点商品设置长过期时间,非热点商品正常过期)long ttl = isHotProduct(productId) ? HOT_CACHE_TTL : CACHE_TTL;redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), ttl, TimeUnit.SECONDS);return product;} else {// 未获取到锁,重试查询缓存(避免直接查库)Thread.sleep(100);return getProductCache(productId);}} catch (InterruptedException e) {throw new RuntimeException("获取商品缓存失败", e);} finally {// 释放锁(确保锁一定会释放)if (lock.isHeldByCurrentThread()) {lock.unlock();}}}/*** 判断是否为热点商品(简化实现:如销量Top1000)*/private boolean isHotProduct(String productId) {List<String> hotProductIds = productService.listHotProductIds(1000);return hotProductIds.contains(productId);}
}
2.3 缓存一致性保障:更新数据库后主动刷新缓存
针对“用户返利余额更新后缓存与数据库不一致”的场景,采用“更新数据库后主动删除旧缓存+延迟双删”策略,代码如下:
package cn.juwatech.rebate.service.impl;import cn.juwatech.rebate.cache.service.UserCacheService;
import cn.juwatech.rebate.entity.UserRebate;
import cn.juwatech.rebate.mapper.UserRebateMapper;
import cn.juwatech.rebate.service.UserRebateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;/*** 用户返利服务实现(含缓存一致性保障)*/
@Service
public class UserRebateServiceImpl implements UserRebateService {private static final String CACHE_KEY_USER_REBATE = "rebate:user:balance:%s";@Autowiredprivate UserRebateMapper userRebateMapper;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate UserCacheService userCacheService;/*** 增加用户返利余额(更新数据库+主动刷新缓存)*/@Override@Transactional(rollbackFor = Exception.class)public void addRebateBalance(String userId, BigDecimal amount) {// 1. 更新数据库(本地事务)UserRebate userRebate = userRebateMapper.selectByUserId(userId);if (userRebate == null) {userRebate = new UserRebate();userRebate.setUserId(userId);userRebate.setBalance(amount);userRebateMapper.insert(userRebate);} else {userRebate.setBalance(userRebate.getBalance().add(amount));userRebateMapper.updateById(userRebate);}// 2. 主动删除旧缓存(避免脏读)String cacheKey = String.format(CACHE_KEY_USER_REBATE, userId);redisTemplate.delete(cacheKey);// 3. 延迟双删(应对极端场景:删除缓存后、更新数据库前的旧请求)delayDeleteCache(cacheKey, 100);// 4. 可选:主动刷新缓存(若后续查询频繁,避免缓存穿透)userCacheService.refreshUserRebateCache(userId);}/*** 延迟删除缓存(异步执行)*/@Asyncpublic void delayDeleteCache(String cacheKey, long delayMillis) {try {Thread.sleep(delayMillis);redisTemplate.delete(cacheKey);} catch (InterruptedException e) {e.printStackTrace();}}
}
2.4 Redis集群配置(Spring Boot)
通过Spring Boot配置Redis Cluster集群节点、连接池与序列化方式,代码如下:
# application.yml 中Redis集群配置
spring:redis:# 集群节点(主从节点均配置,客户端自动识别主从)cluster:nodes:- redis-node1:6379- redis-node2:6379- redis-node3:6379- redis-node4:6379- redis-node5:6379- redis-node6:6379# 最大重定向次数(集群分片路由)max-redirects: 3# 连接池配置(lettuce连接池,性能优于jedis)lettuce:pool:max-active: 32 # 最大连接数max-idle: 16 # 最大空闲连接数min-idle: 8 # 最小空闲连接数max-wait: 3000 # 最大等待时间(毫秒)# 密码(若集群启用密码认证)password: RebateRedis@2024# 超时时间timeout: 5000# Redisson配置(分布式锁、分布式集合等)
redisson:cluster-servers-config:node-addresses:- "redis://redis-node1:6379"- "redis://redis-node2:6379"- "redis://redis-node3:6379"password: RebateRedis@2024# 连接超时时间connect-timeout: 3000# 重试次数retry-attempts: 3# 重试间隔时间retry-interval: 1000
三、Redis集群运维与监控优化
- 内存碎片优化:开启Redis的
activedefrag
(主动碎片整理),配置active-defrag-ignore-bytes 100mb
,当碎片率超过10%时自动整理; - 慢查询监控:设置
slowlog-log-slower-than 10000
(记录超过10ms的命令),通过slowlog get
分析慢查询,优化缓存Key设计与命令使用(如避免KEYS *
); - 集群扩容策略:当单个主节点内存占用超过80%时,新增主从节点并通过
redis-cli --cluster add-node
加入集群,再执行reshard
重新分配哈希槽; - 数据备份:开启Redis的RDB持久化(
save 3600 1
),结合AOF持久化(appendonly yes
),确保数据在集群故障时可恢复。
本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!