SpringBoot + Redis 实战:缓存穿透、雪崩、击穿解决方案

🎓博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
📖全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
👉感兴趣的可以先收藏起来,希望帮助更多的人
在这里插入图片描述

SpringBoot + Redis 实战:缓存穿透、雪崩、击穿解决方案

一、引言

在现代的互联网应用中,缓存是提高系统性能和响应速度的重要手段之一。Redis 作为一款高性能的内存数据库,被广泛应用于缓存场景。而 Spring Boot 作为一个快速开发框架,与 Redis 结合可以轻松实现缓存功能。然而,在使用 Redis 缓存时,会遇到缓存穿透、雪崩、击穿等问题,这些问题如果不加以解决,会严重影响系统的稳定性和性能。本文将详细介绍这些问题的产生原因,并给出相应的解决方案,同时结合 Spring Boot 进行实战演示。

二、Redis 缓存基础回顾

2.1 Redis 简介

Redis 是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(ZSet)等,这使得它在不同的应用场景中都能发挥出色的性能。

2.2 Spring Boot 集成 Redis

在 Spring Boot 项目中集成 Redis 非常简单,只需要添加相应的依赖,并进行简单的配置即可。

2.2.1 添加依赖

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2.2 配置 Redis

application.propertiesapplication.yml 中配置 Redis 连接信息:

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
2.2.3 使用 RedisTemplate

在 Spring Boot 中,可以使用 RedisTemplate 来操作 Redis 缓存。以下是一个简单的示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

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

三、缓存穿透问题及解决方案

3.1 缓存穿透的定义和原因

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样每次请求都会直接访问数据库,导致数据库压力过大。常见的原因是恶意攻击,攻击者可能会发送大量不存在的请求来消耗系统资源。

3.2 解决方案

3.2.1 布隆过滤器

布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于一个集合中。在缓存穿透的场景中,可以使用布隆过滤器来过滤掉那些不存在的请求。

以下是一个使用 Google Guava 实现布隆过滤器的示例:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.nio.charset.Charset;

@Service
public class BloomFilterService {

    private BloomFilter<String> bloomFilter;

    @PostConstruct
    public void init() {
        // 预计插入的数据量
        int expectedInsertions = 1000000;
        // 误判率
        double fpp = 0.001;
        bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
        // 模拟初始化布隆过滤器
        for (int i = 0; i < expectedInsertions; i++) {
            bloomFilter.put("key" + i);
        }
    }

    public boolean mightContain(String key) {
        return bloomFilter.mightContain(key);
    }
}

在业务代码中使用布隆过滤器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private BloomFilterService bloomFilterService;

    public Object getData(String key) {
        if (!bloomFilterService.mightContain(key)) {
            return null;
        }
        Object value = redisService.get(key);
        if (value == null) {
            // 从数据库中查询数据
            value = queryFromDatabase(key);
            if (value != null) {
                redisService.set(key, value);
            }
        }
        return value;
    }

    private Object queryFromDatabase(String key) {
        // 模拟从数据库中查询数据
        return null;
    }
}
3.2.2 空值缓存

当从数据库中查询到的数据为空时,也将空值缓存到 Redis 中,并设置一个较短的过期时间。这样下次相同的请求就可以直接从缓存中获取空值,避免了再次访问数据库。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CacheServiceWithNullCache {

    @Autowired
    private RedisService redisService;

    public Object getData(String key) {
        Object value = redisService.get(key);
        if (value == null) {
            // 从数据库中查询数据
            value = queryFromDatabase(key);
            if (value == null) {
                // 缓存空值,设置较短的过期时间
                redisService.set(key, null);
                redisService.expire(key, 60); // 60 秒
            } else {
                redisService.set(key, value);
            }
        }
        return value;
    }

    private Object queryFromDatabase(String key) {
        // 模拟从数据库中查询数据
        return null;
    }
}

四、缓存雪崩问题及解决方案

4.1 缓存雪崩的定义和原因

缓存雪崩是指在某一时刻,大量的缓存同时过期,导致大量的请求直接访问数据库,从而使数据库压力过大,甚至可能导致数据库崩溃。常见的原因是缓存服务器宕机或大量缓存设置了相同的过期时间。

4.2 解决方案

4.2.1 随机过期时间

为每个缓存设置随机的过期时间,避免大量缓存同时过期。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class CacheServiceWithRandomExpire {

    @Autowired
    private RedisService redisService;

    public void setData(String key, Object value) {
        redisService.set(key, value);
        // 随机生成过期时间,范围在 1 到 10 分钟之间
        Random random = new Random();
        int expireTime = 60 + random.nextInt(540);
        redisService.expire(key, expireTime);
    }

    public Object getData(String key) {
        return redisService.get(key);
    }
}
4.2.2 缓存预热

在系统启动时,将一些常用的数据提前加载到缓存中,并设置合理的过期时间。这样可以避免在系统运行初期出现大量的缓存穿透和雪崩问题。

4.2.3 分布式锁

使用分布式锁来控制对数据库的访问,当大量缓存过期时,只有获取到锁的请求才能访问数据库,其他请求需要等待。可以使用 Redis 的分布式锁来实现。

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 CacheServiceWithDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Object getData(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            // 获取分布式锁
            Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock:" + key, "lock", 10, TimeUnit.SECONDS);
            if (lock != null && lock) {
                try {
                    // 从数据库中查询数据
                    value = queryFromDatabase(key);
                    if (value != null) {
                        redisTemplate.opsForValue().set(key, value);
                    }
                } finally {
                    // 释放锁
                    redisTemplate.delete("lock:" + key);
                }
            } else {
                // 等待一段时间后重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getData(key);
            }
        }
        return value;
    }

    private Object queryFromDatabase(String key) {
        // 模拟从数据库中查询数据
        return null;
    }
}

五、缓存击穿问题及解决方案

5.1 缓存击穿的定义和原因

缓存击穿是指某个热点数据的缓存过期后,大量的请求同时访问该数据,导致这些请求直接访问数据库,从而使数据库压力过大。

5.2 解决方案

5.2.1 永不过期

对于热点数据,可以设置为永不过期,然后在后台使用定时任务来更新缓存。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class CacheServiceWithNeverExpire {

    @Autowired
    private RedisService redisService;

    public Object getData(String key) {
        return redisService.get(key);
    }

    @Scheduled(fixedRate = 3600000) // 每小时更新一次缓存
    public void updateCache() {
        // 更新热点数据的缓存
        String key = "hotKey";
        Object value = queryFromDatabase(key);
        if (value != null) {
            redisService.set(key, value);
        }
    }

    private Object queryFromDatabase(String key) {
        // 模拟从数据库中查询数据
        return null;
    }
}
5.2.2 分布式锁

与缓存雪崩的解决方案类似,使用分布式锁来控制对数据库的访问,当热点数据的缓存过期时,只有获取到锁的请求才能访问数据库,其他请求需要等待。

六、总结

通过本文的介绍,我们了解了 Redis 缓存穿透、雪崩、击穿问题的产生原因,并给出了相应的解决方案。在实际应用中,需要根据具体的业务场景选择合适的解决方案,以确保系统的稳定性和性能。同时,我们还结合 Spring Boot 进行了实战演示,展示了如何在 Spring Boot 项目中使用 Redis 缓存,并解决缓存相关的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanxbl957

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值