💬 1、简述
在现代 Web 应用中,数据库往往是系统性能的瓶颈之一。为了提高查询效率、减轻数据库压力,我们通常会引入 Redis 作为缓存层。本文将介绍在实际项目中如何合理使用 Redis 缓存,并给出实战样例。
🧪 2、为什么使用 Redis 作为缓存?
优势 | 说明 |
---|---|
超快访问速度 | Redis 是内存数据库,读写性能远高于磁盘数据库 |
支持丰富数据结构 | 字符串、哈希、列表、集合、有序集合 |
支持过期策略 | 自动清除过期数据,适合缓存场景 |
原子操作强 | 适合并发环境,支持事务和 Lua 脚本 |
🚀 项目中缓存使用场景 :
✅ 高性能缓存层
- 数据库查询缓存:减轻数据库压力
- 页面/片段缓存:加速Web响应
- 热点数据缓存:应对突发流量
✅ 分布式系统支持
- 会话存储:分布式Session管理
- 全局配置:动态参数配置中心
- 分布式锁:跨进程互斥控制
✅ 特殊数据结构应用
- 排行榜:利用ZSET实现
- 计数器:INCR/DECR原子操作
- 实时统计:HyperLogLog基数估算
🔄 3、基础缓存模式实践
3.1 缓存查询结果(经典Cache-Aside)
// 商品服务查询示例
public Product getProduct(Long id) {
String cacheKey = "product:" + id;
// 1. 先查缓存
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 2. 查数据库
product = productDao.findById(id);
if (product == null) {
return null;
}
// 3. 写入缓存(设置过期时间)
redisTemplate.opsForValue().set(
cacheKey,
product,
30,
TimeUnit.MINUTES
);
return product;
}
优化点:
- 设置合理的TTL(如30分钟)
- 使用双重检查锁避免缓存击穿
- 考虑使用布隆过滤器预防缓存穿透
3.2 缓存更新策略
写穿透(Write-Through)
@Transactional
public void updateProduct(Product product) {
// 1. 更新数据库
productDao.update(product);
// 2. 更新缓存
String cacheKey = "product:" + product.getId();
redisTemplate.opsForValue().set(
cacheKey,
product,
30,
TimeUnit.MINUTES
);
}
延迟删除(Write-Behind)
@Transactional
public void updateProduct(Product product) {
// 1. 只更新数据库
productDao.update(product);
// 2. 异步更新缓存(通过消息队列)
kafkaTemplate.send("cache-refresh",
new CacheRefreshEvent("product", product.getId()));
}
🛠️ 4、典型应用场景实现
4.1 分布式Session管理
Spring Session配置
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class RedisSessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("redis-host", 6379));
}
}
使用示例
// 设置Session属性
@RequestMapping("/login")
public String login(HttpSession session) {
session.setAttribute("user", currentUser);
return "dashboard";
}
4.2 分布式锁实现
RedLock方案
public boolean tryLock(String lockKey, long expireSeconds) {
String lockId = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockId, expireSeconds, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
// 获取锁成功
return true;
}
return false;
}
public void unlock(String lockKey, String lockId) {
// 使用Lua脚本保证原子性
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
lockId
);
}
4.3 热点商品秒杀
public boolean seckill(Long productId, Long userId) {
String stockKey = "seckill:stock:" + productId;
String boughtKey = "seckill:users:" + productId;
// 1. 检查是否已购买
if (redisTemplate.opsForSet().isMember(boughtKey, userId)) {
return false;
}
// 2. 扣减库存(Lua脚本保证原子性)
String script =
"local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock > 0 then " +
" redis.call('decr', KEYS[1]) " +
" redis.call('sadd', KEYS[2], ARGV[1]) " +
" return 1 " +
"end " +
"return 0";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(stockKey, boughtKey),
userId.toString()
);
return result == 1;
}
🌐 5、高级缓存模式
5.1 多级缓存架构
// 多级缓存查询
public Product getProductWithMultiCache(Long id) {
// 1. 检查本地缓存
Product product = localCache.get(id);
if (product != null) {
return product;
}
// 2. 检查Redis缓存
product = redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
localCache.put(id, product); // 回填本地缓存
return product;
}
// 3. 查询数据库
product = productDao.findById(id);
if (product != null) {
redisTemplate.opsForValue().set(
"product:" + id,
product,
10,
TimeUnit.MINUTES
);
localCache.put(id, product);
}
return product;
}
5.2 缓存预热策略
@Component
public class CacheWarmUp implements CommandLineRunner {
@Autowired
private ProductDao productDao;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Override
public void run(String... args) {
// 加载热销商品前100名
List<Product> hotProducts = productDao.findHotProducts(100);
hotProducts.forEach(p -> {
redisTemplate.opsForValue().set(
"product:" + p.getId(),
p,
1,
TimeUnit.HOURS
);
});
}
}
✍️ 6、性能优化实践
6.1 管道化(Pipeline)操作
public Map<Long, Product> batchGetProducts(List<Long> ids) {
List<Object> results = redisTemplate.executePipelined(
(RedisCallback<Object>) connection -> {
ids.forEach(id -> {
connection.stringCommands().get(("product:" + id).getBytes());
});
return null;
}
);
Map<Long, Product> productMap = new HashMap<>();
for (int i = 0; i < results.size(); i++) {
Product p = (Product) results.get(i);
if (p != null) {
productMap.put(ids.get(i), p);
}
}
return productMap;
}
6.2 大Key拆分方案
// 将大Hash拆分为多个小Hash
public void setLargeObject(String key, Map<String, String> bigData) {
int segment = 0;
Map<String, String> segmentMap = new HashMap<>();
for (Map.Entry<String, String> entry : bigData.entrySet()) {
segmentMap.put(entry.getKey(), entry.getValue());
if (segmentMap.size() >= 1000) {
redisTemplate.opsForHash().putAll(
key + ":segment:" + segment,
segmentMap
);
segment++;
segmentMap.clear();
}
}
if (!segmentMap.isEmpty()) {
redisTemplate.opsForHash().putAll(
key + ":segment:" + segment,
segmentMap
);
}
}
📊 7、监控与问题排查
7.1 关键监控指标
# 内存使用
redis-cli info memory | grep used_memory_human
# 命中率
redis-cli info stats | grep keyspace_hits
redis-cli info stats | grep keyspace_misses
# 慢查询
redis-cli slowlog get 5
7.2 缓存雪崩预防
// 差异化过期时间
private int getRandomTtl() {
return 1800 + new Random().nextInt(300); // 1800-2100秒
}
public void setWithRandomTtl(String key, Object value) {
redisTemplate.opsForValue().set(
key,
value,
getRandomTtl(),
TimeUnit.SECONDS
);
}
🧠 8、最佳实践
✅ 键命名规范:
- 使用冒号分隔层级(如
service:entity:id
) - 避免特殊字符
- 控制键长度(不超过100字节)
✅ 过期策略:
- 所有缓存必须设置TTL
- 热点数据永不过期需有更新机制
- 大对象设置较短TTL
✅ 序列化选择:
// 推荐配置
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}
✅ 集群规范:
- 单个Value不超过10KB
- 单个实例键数量不超过1千万
- 避免使用KEYS命令
通过合理应用Redis缓存,可以显著提升系统性能。建议根据业务特点选择合适的缓存模式,并建立完善的监控体系。记住:缓存不是万能的,要始终保证数据最终一致性。
🔚 9、总结
在项目中合理使用 Redis 缓存,不仅能显著提升系统性能,还能提升用户体验。本文介绍了最常用的“旁路缓存”策略、代码实战以及常见问题的应对方式,适用于大多数 Web 后端项目。
🔐 使用 Redis 缓存的核心建议:
把频繁访问、不常变的数据缓存起来
设置合理的过期时间,避免占用内存
缓存更新需和数据库同步或清理旧缓存
注意并发场景下的缓存击穿/雪崩