首页>软件资讯>常见问题

常见问题

Redis缓存击穿

发布时间:2025-12-18 09:04:31人气:5

一、Bug 场景


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


二、代码示例


商品服务(有缺陷)


import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;@Servicepublic 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 命令实现)保证只有一个请求去查询数据库并更新缓存,其他请求等待该请求完成后从缓存获取数据。


import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Servicepublic 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;    }}


2.  **永不过期策略**:对于热门商品,设置缓存不过期,同时使用一个异步线程定时更新缓存数据,确保数据的实时性。


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;@Servicepublic 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);        }    }}



上一条:Redis 核心知识点:持久化、高可用、常见问题

下一条:Redis漏洞图形化利用工具