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;
    }
相关推荐
hanbarger34 分钟前
分布式通信,微服务协调组件,zookeeper
分布式·zookeeper·中间件
qq_372006861 小时前
浏览器http缓存问题
网络协议·http·缓存
计算机毕设定制辅导-无忧学长1 小时前
Redis 初相识:开启缓存世界大门
数据库·redis·缓存
爱吃南瓜的北瓜1 小时前
双重判定锁来解决缓存击穿问题
缓存
Rverdoser1 小时前
redis延迟队列
数据库·redis·缓存
郭源潮3452 小时前
Hadoop
大数据·hadoop·分布式
weisian1512 小时前
Redis篇--常见问题篇6--缓存一致性1(Mysql和Redis缓存一致,更新数据库删除缓存策略)
数据库·redis·缓存
记得开心一点嘛3 小时前
高并发处理 --- Caffeine内存缓存库
缓存·caffeine
Allen Bright3 小时前
RabbitMQ中的普通Confirm模式:深入解析与最佳实践
分布式·rabbitmq
李昊哲小课4 小时前
deepin 安装 kafka
大数据·分布式·zookeeper·数据分析·kafka