分布式缓存实战:Redis与多级缓存架构的完整指南
大家好,我是迪哥。缓存是提升系统性能的关键组件,从本地缓存到分布式缓存,从 Redis 到多级缓存架构,我们经历了多次优化。今天就聊聊分布式缓存的最佳实践。
多级缓存架构
┌─────────────────────────────────────────────────────────────┐
│ 多级缓存架构 │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 本地缓存 │ │ Redis │ │ 数据库 │ │
│ │ Caffeine │ │ 分布式缓存 │ │ MySQL │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
本地缓存:Caffeine
配置示例
java
@Configuration
public class CaffeineConfig {
@Bean
public Cache<String, Object> localCache() {
return Caffeine.newBuilder()
.initialCapacity(1000)
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.expireAfterAccess(5, TimeUnit.MINUTES)
.recordStats()
.build();
}
}
使用示例
java
@Service
public class UserService {
@Autowired
private Cache<String, User> localCache;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
public User getUser(Long userId) {
String key = "user:" + userId;
// 1. 先查本地缓存
User user = localCache.getIfPresent(key);
if (user != null) {
return user;
}
// 2. 再查 Redis
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
user = JSON.parseObject(json, User.class);
localCache.put(key, user);
return user;
}
// 3. 最后查数据库
user = userMapper.selectById(userId);
if (user != null) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
localCache.put(key, user);
}
return user;
}
}
Redis 缓存优化
缓存策略
java
public enum CacheStrategy {
READ_THROUGH, // 读穿透
WRITE_THROUGH, // 写穿透
WRITE_BEHIND, // 写回
CACHE_ASIDE; // 旁路缓存(最常用)
}
缓存更新模式
java
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redisTemplate;
// 模式1:先更新数据库,再删除缓存
public void updateUser1(User user) {
userMapper.updateById(user);
redisTemplate.delete("user:" + user.getId());
}
// 模式2:先删除缓存,再更新数据库
public void updateUser2(User user) {
redisTemplate.delete("user:" + user.getId());
userMapper.updateById(user);
}
// 模式3:延迟双删(解决并发问题)
@Async
public void updateUser3(User user) {
redisTemplate.delete("user:" + user.getId());
userMapper.updateById(user);
// 延迟一段时间后再次删除
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
redisTemplate.delete("user:" + user.getId());
}
}
缓存问题解决
问题 1:缓存穿透
java
// 使用布隆过滤器
@Component
public class BloomFilterService {
private final BloomFilter<Long> userFilter;
public BloomFilterService() {
userFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000, // 预计元素数量
0.01 // 误判率
);
// 初始化过滤器
List<Long> userIds = userMapper.selectAllIds();
userIds.forEach(userFilter::put);
}
public boolean mightContain(Long userId) {
return userFilter.mightContain(userId);
}
}
问题 2:缓存击穿
java
@Service
public class ProductService {
@Autowired
private StringRedisTemplate redisTemplate;
private final Map<String, Object> locks = new ConcurrentHashMap<>();
public Product getProduct(Long productId) {
String key = "product:" + productId;
// 先查缓存
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
return JSON.parseObject(json, Product.class);
}
// 加锁,防止缓存击穿
synchronized (locks.computeIfAbsent(key, k -> new Object())) {
try {
// 双重检查
json = redisTemplate.opsForValue().get(key);
if (json != null) {
return JSON.parseObject(json, Product.class);
}
// 查数据库
Product product = productMapper.selectById(productId);
if (product != null) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 5, TimeUnit.MINUTES);
}
return product;
} finally {
locks.remove(key);
}
}
}
}
问题 3:缓存雪崩
java
// 解决方案:设置随机过期时间
public void setCacheWithRandomTTL(String key, Object value) {
int ttl = 30 + ThreadLocalRandom.current().nextInt(30); // 30-60分钟
redisTemplate.opsForValue().set(key, JSON.toJSONString(value), ttl, TimeUnit.MINUTES);
}
最佳实践清单
| 维度 | 最佳实践 |
|---|---|
| 多级缓存 | 本地缓存 + Redis + 数据库 |
| 缓存策略 | 旁路缓存模式 |
| 更新模式 | 先更新数据库,再删除缓存 |
| 并发问题 | 延迟双删 + 分布式锁 |
| 监控 | 监控缓存命中率、热点Key |
说到缓存,我家那只叫 Docker 的哈士奇最近学会了"缓存策略"------把喜欢的玩具藏在沙发底下(本地缓存),饿了才去狗粮碗(数据库)找吃的,这缓存命中率比我们的 Redis 还高 😂
我是迪哥,我们下期再见!