Redis三大缓存问题-缓存雪崩原因原理及代码解决方案

概念:

缓存雪崩:是指在缓存层面发生的现象,当大量的缓存数据几乎在同一时间内失效过期,导致所有的请求都直接落到数据库上,从而可能引起数据库压力过大、甚至宕机的问题

缓存雪崩可能由以下几个原因:

  1. 缓存同一时间过期:如果大量缓存数据设置了相同的过期时间,这些缓存将在同一时刻失效。

  2. 缓存服务宕机:当缓存服务如 Redis 发生故障或重启,所有的数据都会突然不可用,导致所有请求都转向数据库。

  3. 系统错误:由于系统错误或者配置错误,导致缓存数据被意外清空或失效。

  4. 资源紧张:在高流量或DDoS攻击下,缓存服务可能因为资源紧张(如内存不足)而无法维持正常运作,导致缓存数据丢失。

缓存雪崩的原理可以通过以下步骤来解释:

  1. 当缓存中的数据大规模失效时,新的请求无法在缓存中得到响应,因此转向数据库请求数据。

  2. 数据库需要处理这些原本由缓存处理的请求,导致数据库的读负载急剧增加。

  3. 如果请求量过大,超出了数据库的处理能力,数据库可能会变得响应缓慢或者完全宕机。

  4. 数据库宕机后,所有的请求都会失败,整个系统可能会因此停止服务,造成更加严重的连锁反应。

原理:大量缓存数据同时失效,导致所有请求都直接访问数据库。

实际例子:由于缓存层服务重启或者大规模缓存过期,突然所有的请求都绕过缓存直接打到数据库上。

理论解决方案:

  • 缓存数据的过期时间分散设置:给缓存数据设置不同的随机过期时间,避免大量缓存同时过期。

  • 使用高可用的缓存架构:比如 Redis 集群,确保缓存服务的高可用性。

  • 限流降级:在系统访问压力增大时,启动限流降级策略,确保核心服务可用。

解决方案1: 过期时间分散设置:为防止缓存雪崩,可以在缓存时给每个 key 的过期时间加上一个随机值

typescript 复制代码
import org.springframework.cache.annotation.CachePut;

public class UserService {

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        User updatedUser = updateUserInDatabase(user);
        // 设置带有随机偏移的过期时间
        cacheUserWithRandomExpiration(updatedUser);
        return updatedUser;
    }

    private void cacheUserWithRandomExpiration(User user) {
  long expiration = getExpirationWithJitter();
        // 缓存用户并设置过期时间
        // ...
    }

    private long getExpirationWithJitter() {
        // 获取一个随机的过期时间
        // ...
    }
}

解决方案2:限流降级是一种常用的策略,用于在系统压力过大时临时限制访问频率,保护系统稳定运行。在面临缓存雪崩时,限流降级可以防止大量请求同时打到数据库上,从而避免数据库过载

下面是一个简单的 Java 代码示例,展示如何使用 Sentinel 实现限流降级。Sentinel 是阿里巴巴开源的一款轻量级的流量控制、熔断降级的库

1.首先,添加 Sentinel 的依赖到你的项目中

xml 复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.1</version>
</dependency>

2.然后,配置 Sentinel 规则

java 复制代码
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;

import java.util.ArrayList;
import java.util.List;

public class SentinelConfig {

    public static void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("getHotspotData");
        // 使用异常数作为熔断依据
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        // 设置熔断触发的最小异常数
        rule.setCount(5);
        // 设置熔断的时间窗口,单位为秒
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }
}

3.业务代码中使用 Sentinel 进行保护

typescript 复制代码
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;

public class HotspotDataService {

    public Object getHotspotData(String key) {
        Entry entry = null;
        try {
            // Sentinel 保护的资源名
            entry = SphU.entry("getHotspotData");
            // 正常的业务逻辑
            return getDataFromCacheOrDB(key);
        } catch (BlockException ex) {
            // 如果被限流或降级了,则进入这个代码块
            return handleBlockException(key, ex);
        } catch (Exception ex) {
            // 业务异常
            Tracer.trace(ex);
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
    private Object getDataFromCacheOrDB(String key) {
        // 获取数据的逻辑
        // ...
        return new Object();
    }

    private Object handleBlockException(String key, BlockException ex) {
        // 处理被限流或降级的逻辑,比如返回一个默认值或错误提示
        // ...
        return "Request Blocked";

上面的代码中,getHotspotData 方法被 Sentinel 保护。如果请求达到阈值,Sentinel 会抛出 BlockException,我们可以捕获这个异常并进行相应的降级处理,例如返回一个默认值或者错误提示.

解决方案3:本地缓存+分布式缓存

ini 复制代码
// Java伪代码  
Object value = localCache.get(key);  
if (value == null) {  
    value = redisCache.get(key);  
    if (value != null) {  
        localCache.put(key, value);  
    }  
}
相关推荐
liang_jy11 小时前
Android UID
android·面试
C雨后彩虹11 小时前
任务总执行时长
java·数据结构·算法·华为·面试
小鸡吃米…11 小时前
Python编程语言面试问题二
开发语言·python·面试
算法与双吉汉堡12 小时前
【短链接项目笔记】Day2 用户注册
java·redis·笔记·后端·spring
LYFlied12 小时前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
zwjapple13 小时前
全栈开发面试高频算法题
算法·面试·职场和发展
程序员爱钓鱼13 小时前
Node.js 编程实战:Redis缓存与消息队列实践
后端·面试·node.js
用户479492835691515 小时前
0.1加0.2为什么不等于0.3-答不上来的都挂了
前端·javascript·面试
南山安15 小时前
React学习:Vite+React 基础架构分析
javascript·react.js·面试