redis缓存击穿,redisson分布式锁,redis逻辑过期

什么是缓存击穿:

缓存击穿是指在高并发环境下,某个热点数据的缓存过期,导致大量请求同时访问后端存储系统,引起系统性能下降和后端存储压力过大的现象。

解决方案:
1. redisson分布式锁

本质上是缓存重建的过程中,大量的请求访问到后端的数据库导致数据库压力过大

那么可以使用redisson分布式锁来对缓存重建的过程加锁

其它的线程只有缓存重建完毕之后才可以访问

缺点:所有的请求都要等待拿到锁的线程来进行缓存重建

优点:数据拥有高一致性,适用于某些涉及"钱"的业务,或者要求数据的强一致性的。

  1. 新建redisson子工程单独作为微服务名字叫redisson-starter
  2. 引入redisson相关依赖
java 复制代码
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.17.5</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
  1. 项目结构

    RedissonConfigProperties:一些redisson需要的配置项,如果是集群此处不能用这种方式
java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "redisson-lock")
public class RedissonConfigProperties {

    private String redisHost;

    private String redisPort;

}

RedissonConfig:配置RedissonClient,并且加入了一些自动装配的配置

java 复制代码
import com.yonchao.redisson.service.RedissonLockService;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Data
@Configuration
@EnableConfigurationProperties({RedissonConfigProperties.class})
//当引入Service接口时
@ConditionalOnClass(RedissonLockService.class)
public class RedissonConfig {


    @Autowired
    private RedissonConfigProperties redissonConfigProperties;

    /**
     * 对 Redisson 的使用都是通过 RedissonClient 对象
     * @return
     */
    @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() {
        // 1.创建配置
        Config config = new Config();
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        // 2.根据 Config 创建出 RedissonClient 示例。
        config.useSingleServer().setAddress("redis://"+redissonConfigProperties.getRedisHost()+":"+redissonConfigProperties.getRedisPort());
        return Redisson.create(config);
    }
}

spring.factories:

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.yonchao.redisson.service.RedissonLockService

业务类:

java 复制代码
import com.yonchao.redisson.service.RedissonLockService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedissonLockServiceImpl implements RedissonLockService {
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 加锁
     * @param lockKey
     * @return
     */
    public boolean acquireLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    /**
     * 释放锁
     * @param lockKey
     * @return
     */
    public void releaseLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }
}
  1. 其它微服务通过pom引入redisson-starter微服务
  2. 重建缓存过程中使用分布式锁
    首先注入RedissonClient
    接着判断布隆过滤器
    接着从缓存中读数据
    读不到的话需要重建缓存
    重建缓存:
    首先获取分布式锁
    获取成功了就查询数据库并且重建缓存返回数据最后释放锁
    获取分布式锁失败就等待1秒接着递归这个方法,直到有一个线程重建缓存成功。
java 复制代码
    /**
     * 使用逻辑过期的方式
     * @param id
     * @return
     */
    @Override
    public ResponseResult selectArticleLogicalExpiration(Long id) {
        // 首先经过布隆过滤器
        // 判断这个id是不是在布隆过滤器中
        boolean mightContain = bloomFilter.mightContain(id);

        // 不存在直接返回
        if (!mightContain) {
            return ResponseResult.okResult();
        }
        
        // 首先从缓存中获取数据
        Object articleObj = redisTemplate.opsForValue().get(ARTICLE_KEY + id);
        
        if (Objects.nonNull(articleObj)){

            String articleJSON = (String) articleObj;

            JSONObject jsonObject = JSON.parseObject(articleJSON);

            Long expired = jsonObject.getLong("expired");
            // 旧的文章对象
            ApArticle article = JSON.parseObject(articleJSON, ApArticle.class);
            if (Objects.nonNull(expired)){
                // 未过期直接返回
                if (expired - System.currentTimeMillis() > 0) {

                    return ResponseResult.okResult(article);
                }

                // 过期了进行缓存的重建
                boolean acquiredLock = redissonLockService.acquireLock(lockKeyRedisson);
                // 拿到锁了就 新开一个线程 进行缓存的重建 此处使用分布式锁,只会有一个线程抢占到缓存重建所以不用使用线程池
                if (acquiredLock) {
                    try {
                        new Thread(() -> {
                            // 直接重建缓存,不关心返回值
                            rebuildCache(id);
                        }).start();

                    } finally {
                        // 最后释放锁
                        redissonLockService.releaseLock(lockKeyRedisson);
                    }
                }
                // 开启多线程后直接返回旧的数据
                return ResponseResult.okResult(article);
            }

        }
        // 缓存中根本没有,那么需要直接加锁重建缓存,此时不能多线程的去重建缓存,只能通过分布式锁的方式,
        boolean lockAcquired = redissonLockService.acquireLock(lockKeyLogicalExpiration);
        if (lockAcquired){
            try {
                ApArticle apArticle = rebuildCache(id);
                if (Objects.isNull(apArticle)){
                    // 数据不存在就直接返回
                    return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
                }
                // 返回获得的文章数据
                return ResponseResult.okResult(apArticle);

            } finally {
                // 最后释放锁
                redissonLockService.releaseLock(lockKeyLogicalExpiration);
            }
        } else {
            // 没有获取到锁就等待一段时间然后再次尝试获取锁
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                log.error(e.getMessage());
            }
            // 等待一段时间重新校验有没有缓存
            return selectArticleLogicalExpiration(id);
        }
    }


    public ApArticle rebuildCache(Long id){
        // 重建缓存
        ApArticle articleDatabase = getById(id);
        // 重建缓存
        if (Objects.nonNull(articleDatabase)) {
            // 设置逻辑过期时间
            // 转为jsonString
            String articleJsonString = JSON.toJSONString(articleDatabase);
            // 转为JSONObject
            JSONObject articleJsonObject = JSON.parseObject(articleJsonString);
            // 当前时间戳加上 设置的过期时间*1000 因为时间戳是毫秒
            articleJsonObject.put("expired", System.currentTimeMillis() + ARTICLE_EXPIRED * 1000);
            redisTemplate.opsForValue().set(ARTICLE_KEY + id, JSON.toJSONString(articleJsonObject));
            // 布隆过滤器过滤过的,这个肯定存在
            return articleDatabase;
        }
        return null;
    }
相关推荐
咩咩啃树皮5 小时前
第43篇:Vue3计算属性(computed)完全精讲——缓存机制、依赖计算、业务最优解
前端·vue.js·缓存
向夏威夷 梦断明暄17 小时前
从架构特点到功能缺陷,重新认识分析型分布式数据库
数据库·分布式·架构
海兰17 小时前
【高速缓存】RedisVL 存储类型选择指南:Hash 与 JSON
人工智能·redis·算法·缓存·json·哈希算法
Devin~Y19 小时前
互联网大厂 Java 面试实录:Spring Boot、MyBatis、Redis、Kafka、Spring Security、RAG 与 MCP 全链路问答
java·redis·kafka·mybatis·spring security·spring mvc·sprint boot
AAA@峥20 小时前
Ceph 集群配置管理完整指南
运维·数据库·分布式·ceph
海兰20 小时前
【高速缓存】RedisVL 索引迁移指南:安全演进索引结构
前端·数据库·redis·安全·缓存·bootstrap
海兰20 小时前
【高速缓存】 RedisVL MCP 运行指南(下)
人工智能·redis·哈希算法·高速缓存
心如鉄补20 小时前
分布式链路追踪系统之docker-compose安装skywalking
分布式·docker·skywalking
努力努力再努力wz20 小时前
【分布式系统与 RPC 框架系列】从单机瓶颈到远程调用:一文理解分布式架构与 RPC 原理
linux·网络·c++·分布式·网络协议·rpc·架构
ACP广源盛139246256731 天前
此芯 P1 AI BOX / AI NAS@ACP#IX8008、IX8024 应用场景与市场机会
大数据·人工智能·分布式·单片机·嵌入式硬件