返利软件的分布式缓存架构: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"等穿透场景,通过布隆过滤器拦截无效请求,结合空值缓存避免重复穿透,代码如下:
java
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.01
private BloomFilter<String> productBloomFilter;
// 空值缓存过期时间(5分钟)
private static final long NULL_CACHE_TTL = 300;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductService productService;
// 项目启动时初始化布隆过滤器(加载所有有效商品ID)
@PostConstruct
public void initBloomFilter() {
// 1. 从数据库查询所有有效商品ID
List<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分布式锁保证同一时间只有一个请求更新缓存,代码如下:
java
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;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductService productService;
@Autowired
private CachePenetrationGuard penetrationGuard;
/**
* 获取商品缓存(含穿透、击穿防护)
*/
public ProductDTO getProductCache(String productId) {
String cacheKey = String.format(CACHE_KEY_PRODUCT, productId);
String jsonData;
// 1. 穿透防护:布隆过滤器拦截无效商品ID
if (!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 缓存一致性保障:更新数据库后主动刷新缓存
针对"用户返利余额更新后缓存与数据库不一致"的场景,采用"更新数据库后主动删除旧缓存+延迟双删"策略,代码如下:
java
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";
@Autowired
private UserRebateMapper userRebateMapper;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private 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);
}
/**
* 延迟删除缓存(异步执行)
*/
@Async
public 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集群节点、连接池与序列化方式,代码如下:
yaml
# 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开发者团队,转载请注明出处!