Redis高并发场景下的缓存设计与优化

引言

在高并发电商系统中,Redis 缓存是提升系统性能的关键组件。本文将分享我在实习期间处理日均 10 万+ PV 电商平台的 Redis 优化经验。

缓存架构设计

整体架构

1
2
3
用户请求 → CDN → Nginx → 应用层 → Redis缓存 → MySQL

本地缓存(Caffeine)

多级缓存策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
public class MultiLevelCache {

// L1: 本地缓存
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();

@Autowired
private StringRedisTemplate redisTemplate;

public Object get(String key) {
// L1 查询
Object value = localCache.getIfPresent(key);
if (value != null) {
return value;
}

// L2 Redis 查询
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
return value;
}

return null;
}
}

商品详情缓存优化

缓存预热实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
public class ProductCacheWarmer {

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private ProductService productService;

// 每日凌晨预热热点商品
@Scheduled(cron = "0 0 2 * * ?")
public void warmUpHotProducts() {
// 获取近7天热销商品
List<Long> hotProductIds = productService.getHotProductIds(7, 100);

for (Long productId : hotProductIds) {
ProductDetail detail = productService.getProductDetail(productId);

String cacheKey = "product:detail:" + productId;
redisTemplate.opsForValue().set(
cacheKey,
JSON.toJSONString(detail),
Duration.ofHours(24)
);
}

log.info("缓存预热完成,共预热 {} 个商品", hotProductIds.size());
}
}

缓存更新策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Service
public class ProductCacheService {

@Autowired
private StringRedisTemplate redisTemplate;

// 缓存 key 前缀
private static final String PRODUCT_KEY_PREFIX = "product:detail:";
private static final long CACHE_TTL = 3600; // 1小时

/**
* 查询商品详情(带缓存)
*/
public ProductDetail getProductDetail(Long productId) {
String cacheKey = PRODUCT_KEY_PREFIX + productId;

// 查询缓存
String cached = redisTemplate.opsForValue().get(cacheKey);
if (StringUtils.hasText(cached)) {
return JSON.parseObject(cached, ProductDetail.class);
}

// 查询数据库
ProductDetail detail = productService.queryFromDB(productId);
if (detail != null) {
// 写入缓存
redisTemplate.opsForValue().set(
cacheKey,
JSON.toJSONString(detail),
CACHE_TTL,
TimeUnit.SECONDS
);
}

return detail;
}

/**
* 更新商品时删除缓存
*/
public void updateProduct(ProductDetail product) {
// 先更新数据库
productService.update(product);

// 再删除缓存
String cacheKey = PRODUCT_KEY_PREFIX + product.getId();
redisTemplate.delete(cacheKey);
}
}

分布式锁实现

Redisson 分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
public class DistributedLockManager {

@Autowired
private RedissonClient redissonClient;

/**
* 获取库存锁
*/
public boolean tryLockStock(Long productId, long waitTime, long leaseTime) {
String lockKey = "lock:stock:" + productId;
RLock lock = redissonClient.getLock(lockKey);

try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}

/**
* 释放锁
*/
public void unlockStock(Long productId) {
String lockKey = "lock:stock:" + productId;
RLock lock = redissonClient.getLock(lockKey);

if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}

库存扣减实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Service
public class StockService {

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private DistributedLockManager lockManager;

/**
* Redis 预扣库存 + MySQL 事务补偿
*/
public boolean deductStock(Long productId, Integer quantity) {
String stockKey = "stock:" + productId;
String lockKey = "lock:stock:" + productId;

// 1. Redis 预扣库存
Long remainStock = redisTemplate.opsForValue().decrement(stockKey, quantity);

if (remainStock == null || remainStock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey, quantity);
return false;
}

try {
// 2. 异步同步到数据库
asyncSyncToDB(productId, quantity);

return true;
} catch (Exception e) {
// 异常回滚
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new StockException("库存扣减失败", e);
}
}

@Async
protected void asyncSyncToDB(Long productId, Integer quantity) {
// 数据库库存扣减
productMapper.decreaseStock(productId, quantity);
}
}

缓存问题解决方案

1. 缓存穿透

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Component
public class CachePenetrationSolution {

@Autowired
private StringRedisTemplate redisTemplate;

/**
* 布隆过滤器防止缓存穿透
*/
public ProductDetail getWithBloomFilter(Long productId) {
String bloomKey = "bloom:product";

// 布隆过滤器检查
Boolean mightExist = redisTemplate.opsForValue()
.getBit(bloomKey, productId.hashCode());

if (Boolean.FALSE.equals(mightExist)) {
return null; // 一定不存在
}

// 继续查询缓存或数据库
return getProductDetail(productId);
}

/**
* 空值缓存策略
*/
public ProductDetail getWithNullCache(Long productId) {
String cacheKey = "product:detail:" + productId;
String cached = redisTemplate.opsForValue().get(cacheKey);

// 空值标记
if ("NULL".equals(cached)) {
return null;
}

// 查询数据库...
ProductDetail detail = queryFromDB(productId);

if (detail == null) {
// 缓存空值,防止穿透
redisTemplate.opsForValue().set(
cacheKey,
"NULL",
5,
TimeUnit.MINUTES
);
}

return detail;
}
}

2. 缓存雪崩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
public class CacheAvalancheSolution {

@Autowired
private StringRedisTemplate redisTemplate;

/**
* 随机过期时间,防止同时失效
*/
public void setWithRandomExpire(String key, Object value, long baseExpire) {
// 基础过期时间 + 随机偏移(0-300秒)
long randomOffset = ThreadLocalRandom.current().nextInt(300);
long finalExpire = baseExpire + randomOffset;

redisTemplate.opsForValue().set(
key,
JSON.toJSONString(value),
finalExpire,
TimeUnit.SECONDS
);
}

/**
* 热点数据永不过期 + 异步更新
*/
public void setHotData(String key, Object value) {
// 不设置过期时间
redisTemplate.opsForValue().set(key, JSON.toJSONString(value));

// 记录逻辑过期时间
String logicExpireKey = "expire:" + key;
redisTemplate.opsForValue().set(
logicExpireKey,
String.valueOf(System.currentTimeMillis() + 3600000),
1,
TimeUnit.HOURS
);
}
}

性能优化成果

在实际项目中,通过上述优化措施:

指标 优化前 优化后 提升
商品查询 QPS ~200 ~800 300%
平均响应时间 120ms 80ms 33%
数据库查询量 100% 20% 80%
库存超卖 偶有发生 零超卖 100%

总结

Redis 缓存优化需要综合考虑:

  1. 多级缓存:本地缓存 + Redis 缓存
  2. 预热策略:提前加载热点数据
  3. 更新策略:Cache-Aside 模式
  4. 并发控制:分布式锁 + 原子操作
  5. 异常处理:防穿透、防雪崩、防击穿

合理的缓存设计能够将系统性能提升数倍,是高并发系统的必备技能。


本文基于电商平台实习经验总结,日均 10 万+ PV 场景实践


Redis高并发场景下的缓存设计与优化
https://zxyblog.top/2024/09/10/Redis高并发场景下的缓存设计与优化/
作者
zxy
发布于
2024年9月10日
许可协议