• SpringBoot3集成Redis


    标签:Redis.Mybatis.Lock;

    一、简介

    缓存在项目开发中,基本上是必选组件之一,Redis作为一个key-value存储系统,具备极高的数据读写效率,并且支持的数据类型比较丰富,在业务场景中的应用非常广泛;

    Redis典型的应用场景就是数据缓存能力,用来解决业务中最容易出现的查询性能问题,提升系统的响应效率;其次就是分布式锁机制,用来解决分布式系统中多线程并发处理资源的安全问题;

    二、工程搭建

    1、工程结构

    2、依赖管理

    Redis的客户端使用的是lettuce组件的6.2.4.RELEASE版本,该组件具备可扩展和线程安全的特性,支持Redis各种高级功能,如哨兵,集群,流水线,自动重新连接等;

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
        <version>${spring-boot.version}version>
    dependency>
    
    <dependency>
        <groupId>org.springframework.integrationgroupId>
        <artifactId>spring-integration-redisartifactId>
        <version>${spring-integration.version}version>
    dependency>
    

    3、Redis配置

    配置文件,在lettuce组件中默认使用的是common-pool2连接池组件,在配置中定义几个常用的核心参数;

    spring:
      # Redis配置
      data:
        redis:
          host: 127.0.0.1
          port: 6379
          database: 1
          # 读超时
          timeout: 3000
          # 连接超时
          connectTimeout: 5000
          # Lettuce连接池
          lettuce:
            pool:
              # 最小空闲连接
              min-idle: 5
              # 最大空闲连接
              max-idle: 10
              # 最大连接数
              max-active: 100
              # 连接分配应该阻塞的最大时间
              max-wait: 2000
    

    配置类

    @Configuration
    public class RedisConfig {
    
        /**
         * RedisTemplate模板
         */
        @Bean("redisTemplate")
        public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(factory);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            return redisTemplate;
        }
    
        /**
         * StringRedisTemplate模板
         */
        @Bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
            StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
            stringRedisTemplate.setConnectionFactory(factory);
            stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
            return stringRedisTemplate;
        }
    
        /**
         * Redis分布式锁
         */
        @Bean
        public RedisLockRegistry redisLockRegistry(RedisConnectionFactory factory){
            // 1、锁的密钥前缀:REDIS-LOCK
            // 2、锁的过期时间:20秒
            return new RedisLockRegistry(factory, "REDIS-LOCK",20000L);
        }
    }
    

    三、Redis用法

    1、环境搭建

    Redis文档中推荐使用brew工具安装的方式,本地环境中是4.1版本,可以搜索到redis@6.2,即安装该版本,并且可以使用brew命令管理Redis的启动和停止;

    brew --version
    Homebrew 4.1.0
    
    brew search redis
    ==> Formulae
    redis             redis-leveldb     redis@3.2         redis@4.0         redis@6.2
    
    brew install redis@6.2
    
    brew services start redis@6.2
    ==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2)
    
    brew services restart redis@6.2
    ==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)
    ==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2)
    
    brew services stop redis@6.2
    ==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)
    

    配置Redis环境变量,这样可以在命令行使用Redis原生的指令进行启动和客户端操作,在测试完成之后,Mac系统本地的redis@6.2环境就配置好了;

    open -e ~/.bash_profile
    
    export REDIS_HOME=/usr/local/opt/redis@6.2
    export PATH=$REDIS_HOME/bin:$PATH
    
    source ~/.bash_profile
    
    redis-server
    Redis 6.2.13 ,Running in standalone mode , Port: 6379 , PID: 7966
    
    redis-cli
    127.0.0.1:6379> select 1
    OK
    127.0.0.1:6379> set redis-key redis-value
    OK
    127.0.0.1:6379> get redis-key
    "redis-value"
    127.0.0.1:6379> del redis-key
    (integer) 1
    

    2、数据类型

    Strings:字符串是最基本的数据类型,表示一个字节序列,也是最常使用的类型,单个最大能够存储512MB

    public void dataString () throws Exception {
        // 1、添加K-V缓存
        redisTemplate.opsForValue().set("key-01","value-01",300, TimeUnit.SECONDS);
        // 2、添加JSON格式
        JsonMapper jsonMapper = new JsonMapper() ;
        String jsonVar = jsonMapper.writeValueAsString(new IdValue(1,"Json格式")) ;
        redisTemplate.opsForValue().set("key-02",jsonVar,500,TimeUnit.SECONDS);
        Object key01 = redisTemplate.opsForValue().get("key-01");
        Object key02 = redisTemplate.opsForValue().get("key-02");
        log.info("key01:{},key02:{}",key01,key02);
    }
    

    Lists:链表结构,可以有序的存储多个字符串值,基于数据左写入右读取的方式可以模拟消息队列,单个最大长度为2^32-1

    public void dataList (){
        // 1、左侧写数据
        redisTemplate.opsForList().leftPushAll("data-list","value-01","value-02","value-03");
        // 2、右侧读数据
        Object rightPop01 = redisTemplate.opsForList().rightPop("data-list") ;
        Object rightPop02 = redisTemplate.opsForList().rightPop("data-list") ;
        log.info("rightPop01:{},rightPop02:{}",rightPop01,rightPop02);
    }
    

    Sets:用来存储无序的、唯一的字符串集合,通常用来跟踪分析唯一项的业务场景,比如唯一标签下的用户群,独立IP等;

    public void dataSet (){
        // 1、写数据
        redisTemplate.opsForSet().add("data-set","Java","C++","Python","C++");
        // 2、读数据
        Object pop01 = redisTemplate.opsForSet().pop("data-set");
        Object pop02 = redisTemplate.opsForSet().pop("data-set");
        log.info("pop01:{},pop02:{}",pop01,pop02);
    }
    

    Hashes:Redis本身的K-V结构上,在value中可以再次使用K-V数据类型,在涉及大规模的关系型数据存储时,会优先使用该类型;

    public void dataHash (){
        // 1、写数据
        HashMap hashMap = new HashMap<>() ;
        hashMap.put("key1","value1") ;
        hashMap.put("key2","value2") ;
        redisTemplate.opsForHash().putAll("data-hash",hashMap);
        // 2、读数据
        Object kv1 = redisTemplate.opsForHash().get("data-hash","key1");
        Object kv2 = redisTemplate.opsForHash().get("data-hash","key2");
        log.info("kv1:{},kv2:{}",kv1,kv2);
    }
    

    Sorted-sets:唯一的字符串集合,但是可以通过每个字符串的相关分数来维持顺序,这里的分数可以表示排行顺序,也可以标识为对象的权重或优先级;

    public void dataSortedSet (){
        // 1、写数据
        redisTemplate.opsForZSet().add("sorted-set","Java",1.0);
        redisTemplate.opsForZSet().add("sorted-set","Python",3.0);
        redisTemplate.opsForZSet().add("sorted-set","C++",2.0);
        // 2、读数据
        Object popMax = redisTemplate.opsForZSet().popMax("sorted-set");
        Object popMin = redisTemplate.opsForZSet().popMin("sorted-set");
        log.info("popMax:{},popMin:{}",popMax,popMin);
    }
    

    3、加锁机制

    使用SETNX方法即代码中的setIfAbsent,如果key不存在,则设置key保存字符串的值,也就相当于SET,当key已经存在时,不执行任何操作;

    @Service
    public class RedisLockService {
        @Resource
        private RedisTemplate redisTemplate ;
    
        public Boolean lock (String lock,long timeout){
            return redisTemplate.opsForValue().setIfAbsent(lock,lock,timeout, TimeUnit.SECONDS);
        }
        public void unLock (String lock){
            Object lockVal = redisTemplate.opsForValue().get(lock);
            if (!Objects.isNull(lockVal) && Objects.equals(lockVal.toString(),lock)){
                redisTemplate.delete(lock) ;
            }
        }
    }
    

    RedisLockRegistry是由spring-integration组件提供的封装类,基于Redis实现分布式锁的能力;有关于两种锁机制的测试,可以查看代码工程中RedisLockTest单元测试类,

    @Service
    public class LockRegistryService {
    
        @Resource
        protected RedisLockRegistry redisLockRegistry;
    
        /**
         * 尝试一次加锁
         */
        @SneakyThrows
        public  Boolean tryLock(T lockKey, Long time) {
            return redisLockRegistry.obtain(lockKey).tryLock(time, TimeUnit.SECONDS);
        }
    
        /**
         * 重试机制多次加锁
         */
        @SneakyThrows
        public  Boolean reTryLock(T lockKey, Long time,int retryNum) {
            Boolean lockFlag = tryLock(lockKey, time);
            if (Boolean.TRUE.equals(lockFlag)) {
                return Boolean.TRUE;
            }
            for (int i = 0; i < retryNum; i++) {
                if (Boolean.TRUE.equals(tryLock(lockKey, time))){
                    return Boolean.TRUE;
                }
            }
            return Boolean.FALSE;
        }
    
        /**
         * 释放锁
         */
        public  void unlock(T lockKey) {
            redisLockRegistry.obtain(lockKey).unlock();
        }
    }
    

    四、Mybatis缓存

    1、基础配置

    在Mybatis框架中,一级缓存是指SqlSession级别内;二级缓存是指Mapper级别内,需要在配置文件中开启,二级缓存并不适用于频繁修改数据的业务场景;

    配置文件

    mybatis-plus:
      configuration:
        cache-enabled: true
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    Mapper注解,这里使用自定义的MybatisCache类,并且使用Redis组件来缓存数据;

    @CacheNamespace(implementation = MybatisCache.class)
    public interface ArticleMapper extends BaseMapper
    { }

    2、自定义实现

    通过实现Cache接口,来定义二级缓存的处理策略,可以运行MybatisTest测试类中的方法,分析其执行的原理,符合Cache-Aside模式,即先查询缓存,如果未命中就查询数据库,并将结果写入缓存,数据更新时会删除相关缓存;

    public class MybatisCache implements Cache {
    
        private static final Logger log = LoggerFactory.getLogger(MybatisCache.class);
    
        private RedisTemplate redisTemplate ;
    
        private String id ;
    
        public MybatisCache (String id){
            log.info("mybatis...cache...id:{}",id);
            this.id = id ;
        }
    
        public RedisTemplate getRedisTemplate(){
            if(redisTemplate == null) {
                synchronized (MybatisCache.class) {
                    if(redisTemplate == null) {
                        redisTemplate = SpringContextUtil.getBean("redisTemplate",RedisTemplate.class);
                        return redisTemplate;
                    }
                    return redisTemplate;
                }
            }
            return redisTemplate ;
        }
        @Override
        public String getId() {
            return this.id ;
        }
    
        @Override
        public void putObject(Object key, Object value) {
            log.info("mybatis...cache...put...key:{},value:{}",key,value);
            this.getRedisTemplate().opsForValue().set(String.valueOf(key),value,5, TimeUnit.MINUTES);
        }
    
        @Override
        public Object getObject(Object key) {
            log.info("mybatis...cache...get...key:{}",key);
            return this.getRedisTemplate().opsForValue().get(String.valueOf(key)) ;
        }
    
        @Override
        public Object removeObject(Object key) {
            log.info("mybatis...cache...remove...key:{}",key);
            return this.getRedisTemplate().delete(String.valueOf(key));
        }
    
        @Override
        public void clear() {
            Set keys = this.getRedisTemplate().keys("*" + id + "*");
            if (keys != null && keys.size()>0){
                log.info("mybatis...cache...clear...keys:{}",keys);
                this.getRedisTemplate().delete(keys) ;
            }
        }
    
        @Override
        public int getSize() {
            Set keys = this.getRedisTemplate().keys("*" + id + "*");
            if (keys != null){
                log.info("mybatis...cache...size...keys:{}",keys.size());
                return keys.size() ;
            }
            return 0;
        }
    }
    

    五、参考源码

    文档仓库:
    https://gitee.com/cicadasmile/butte-java-note
    
    源码仓库:
    https://gitee.com/cicadasmile/butte-spring-parent
    
  • 相关阅读:
    每周电子W4——电路与电路模型
    Jenkins 构建报错 Could not load
    Dynamic Lead Time Promising
    【5G NAS】5G SUPI 和 SUCI 标识符详解
    Nacos源码 (7) Nacos与Spring
    alert、consloe、console.error、confirm、prompt
    北斗导航 | 自动驾驶多模态传感器融合综述
    springboot毕设项目畅游旅游网f92py(java+VUE+Mybatis+Maven+Mysql)
    k8s(3)
    微软官方发布的C#开源、免费、实用的Windows工具箱
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/17630594.html