💬 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 多级缓存架构
graph TD
    A[客户端] --> B[CDN]
    B --> C[Nginx缓存]
    C --> D[应用进程缓存]
    D --> E[Redis集群]
    E --> F[数据库]
// 多级缓存查询
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 缓存的核心建议:
🔹 把频繁访问、不常变的数据缓存起来
🔹 设置合理的过期时间,避免占用内存
🔹 缓存更新需和数据库同步或清理旧缓存
🔹 注意并发场景下的缓存击穿/雪崩