Redis 缓存击穿

一、Bug 场景

在一个电商系统中,某些热门商品的查询频率极高。系统使用 Redis 缓存这些商品信息,以减轻数据库压力。当这些热门商品的缓存过期瞬间,大量并发请求同时涌入,由于缓存中已无该商品数据,所有请求直接穿透到数据库,导致数据库负载瞬间过高,甚至可能引发系统雪崩,影响整个系统的稳定性。

二、代码示例

商品服务(有缺陷)

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 假设这是数据库查询方法
    private Object queryProductFromDB(String productId) {
        // 模拟数据库查询逻辑,返回商品信息
        return "Product Data";
    }

    public Object getProduct(String productId) {
        Object product = redisTemplate.opsForValue().get(productId);
        if (product == null) {
            product = queryProductFromDB(productId);
            if (product != null) {
                // 设置缓存,假设过期时间为1小时
                redisTemplate.opsForValue().set(productId, product, 1, TimeUnit.HOURS);
            }
        }
        return product;
    }
}

三、问题描述

  1. 预期行为:即使热门商品缓存过期,系统也能平稳处理请求,数据库负载不会出现激增,系统保持稳定运行。
  2. 实际行为:当热门商品缓存过期时,大量并发请求同时发现缓存为空,进而同时查询数据库。数据库瞬间承受巨大压力,可能导致数据库响应变慢甚至崩溃。这是因为在缓存过期的瞬间,大量请求同时 "击穿" 缓存,直接访问数据库,而系统没有相应的应对机制来分散这些请求。

四、解决方案

  1. 互斥锁(Mutex) :在缓存过期时,使用互斥锁(如 Redis 的 SETNX 命令实现)保证只有一个请求去查询数据库并更新缓存,其他请求等待该请求完成后从缓存获取数据。
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 假设这是数据库查询方法
    private Object queryProductFromDB(String productId) {
        // 模拟数据库查询逻辑,返回商品信息
        return "Product Data";
    }

    public Object getProduct(String productId) {
        Object product = redisTemplate.opsForValue().get(productId);
        if (product == null) {
            String lockKey = "product:lock:" + productId;
            // 使用SETNX命令尝试获取锁
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 1, TimeUnit.MINUTES);
            if (locked) {
                try {
                    product = queryProductFromDB(productId);
                    if (product != null) {
                        redisTemplate.opsForValue().set(productId, product, 1, TimeUnit.HOURS);
                    }
                } finally {
                    // 释放锁
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未获取到锁,等待一段时间后重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return getProduct(productId);
            }
        }
        return product;
    }
}
  1. 永不过期策略:对于热门商品,设置缓存不过期,同时使用一个异步线程定时更新缓存数据,确保数据的实时性。
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 假设这是数据库查询方法
    private Object queryProductFromDB(String productId) {
        // 模拟数据库查询逻辑,返回商品信息
        return "Product Data";
    }

    public Object getProduct(String productId) {
        return redisTemplate.opsForValue().get(productId);
    }

    // 定时任务,每半小时更新一次热门商品缓存
    @Scheduled(fixedRate = 30 * 60 * 1000)
    private void updateHotProductCache() {
        String productId = "hot_product_id";
        Object product = queryProductFromDB(productId);
        if (product != null) {
            // 设置永不过期
            redisTemplate.opsForValue().set(productId, product);
        }
    }
}
相关推荐
snow@li10 分钟前
Java:跨平台原理与JDK、JRE、JVM全景深度解析
java·开发语言·jvm
覆东流15 分钟前
2.Java程序基础
java·开发语言·后端
snow@li20 分钟前
SpringBoot:全套生命周期全景详解/应用级+Bean级
java·spring boot·rpc
love_muming32 分钟前
二叉树操作全解析:从递归到层序遍历
java·数据结构·算法·二叉树
小的~~33 分钟前
ThreadLocal 、InheritableThreadLocal 与 TransmittableThreadLocal 的进阶指南
java·开发语言
北极糊的狐44 分钟前
钉钉小程序报错data.formatTime is not a function是因为 axml 模板中不能直接调用 Page 内自定义方法!
java·小程序·钉钉
·薯条大王10 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
ZJH__GO13 小时前
网络编程v4pro--实现聊天室文件传输功能
java·服务器·网络·计算机网络
程序员黑豆13 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程
命运之光13 小时前
【C语言完整代码】就诊信息管理系统
java·c语言·开发语言