Spring Cache怎么使用Redisson分布式锁解决缓存击穿问题

2023-05-16,,

本篇内容主要讲解“Spring Cache怎么使用Redisson分布式锁解决缓存击穿问题”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Spring Cache怎么使用Redisson分布式锁解决缓存击穿问题”吧!

    1 什么是缓存击穿

    一份热点数据,它的访问量非常大。在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。

    2 为什么要使用分布式锁

    在项目中,当共享资源出现竞争情况的时候,为了防止出现并发问题,我们一般会采用锁机制来控制。在单机环境下,可以使用synchronized或Lock来实现;但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现,比如mysql、redis、zookeeper。

    3 什么是Redisson

    Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

    4 Spring Boot集成Redisson

    4.1 添加maven依赖

    不再需要spring-boot-starter-data-redis依赖,但是都添加也不会报错

    <!--redisson-->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.17.0</version>
    </dependency>

    4.2 配置yml

    spring:
      datasource:
        username: xx
        password: xxxxxx
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=CTT
      cache:
        type: redis
      redis:
        database: 0
        port: 6379               # Redis服务器连接端口
        host: localhost          # Redis服务器地址
        password: xxxxxx         # Redis服务器连接密码(默认为空)
        timeout: 5000            # 超时时间

    4.3 配置RedissonConfig

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.time.Duration;
    import java.util.Random;
    
    @EnableCaching
    @Configuration
    public class RedissonConfig {
    
        @Value("${spring.redis.host}")
        private String host;
    
        @Value("${spring.redis.port}")
        private String port;
    
        @Value("${spring.redis.password}")
        private String password;
    
    
        @Bean(destroyMethod = "shutdown")  // bean销毁时关闭Redisson实例,但不关闭Redis服务
        public RedissonClient redisson() {
            //创建配置
            Config config = new Config();
            /**
             *  连接哨兵:config.useSentinelServers().setMasterName("myMaster").addSentinelAddress()
             *  连接集群: config.useClusterServers().addNodeAddress()
             */
            config.useSingleServer()
                    .setAddress("redis://" + host + ":" + port)
                    .setPassword(password)
                    .setTimeout(5000);
            //根据config创建出RedissonClient实例
            return Redisson.create(config);
        }
    
        @Bean
        public CacheManager RedisCacheManager(RedisConnectionFactory factory) {
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            // 解决查询缓存转换异常的问题
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            /**
             * 新版本中om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)已经被废弃
             * 建议替换为om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL)
             */
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // 配置序列化解决乱码的问题
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置缓存过期时间  为解决缓存雪崩,所以将过期时间加随机值
                    .entryTtl(Duration.ofSeconds(60 * 60 + new Random().nextInt(60 * 10)))
                    // 设置key的序列化方式
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                    // 设置value的序列化方式
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
            // .disableCachingNullValues(); //为防止缓存击穿,所以允许缓存null值
            RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                    .cacheDefaults(config)
                    // 启用RedisCache以将缓存 put/evict 操作与正在进行的 Spring 管理的事务同步
                    .transactionAware()
                    .build();
            return cacheManager;
        }
    }

    5 使用Redisson的分布式锁解决缓存击穿

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.company.dubbodemo.entity.User;
    import com.company.dubbodemo.mapper.UserMapper;
    import com.company.dubbodemo.service.UserService;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User>
            implements UserService {
        
        @Resource
        private RedissonClient redissonClient;
    
        @Resource
        private UserMapper userMapper;
    
        @Override
        // 一定要设置sync = true开启异步,否则会导致多个线程同时获取到锁
        @Cacheable(cacheNames = "user", key = "#id", sync = true)
        public User findById(Long id) {
            /**
             *
             * 加了@Cacheable之后方法体执行说明缓存中不存在所查询的数据
             * 获取一把锁,只要锁的名字一样,就是同一把锁
             */
    
            /**
             * 注意: 如果设置了lock.lock(10,TimeUnit.SECONDS) 锁过期不会自动续期
             *      1、如果我们传递了锁的过期时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
             *      2、如果没有指定锁的超时时间,就使用30000L(LockWatchdogTimeout 看门狗的默认时间)
             *      可通过RedissonConfig-->getRedissonClient()-->config.setLockWatchdogTimeout()设置看门狗时间
             *         只要占锁成功就会启动一个定时任务【就会重新给锁设置过期时间,新的时间就是看门狗的默认时间】,每隔10s都会自动续期,续期成30s
             * 看门狗机制
             * 1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s。不用担心因为业务时间长,锁自动过期被删除
             * 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除
             *
             */
            RLock lock = redissonClient.getLock("redissonClient-lock");
    
            // 对第一个线程执行方法体的线程加锁,加了@Cacheable,方法执行之后会将方法的返回值存入缓存,下一个线程直接读取缓存
            lock.lock();
            User user = new User();
            try {
                user = userMapper.selectById(id);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return user;
        }
    }

    到此,相信大家对“Spring Cache怎么使用Redisson分布式锁解决缓存击穿问题”有了更深的了解,不妨来实际操作一番吧!这里是本站网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    《Spring Cache怎么使用Redisson分布式锁解决缓存击穿问题.doc》

    下载本文的Word格式文档,以方便收藏与打印。