ToB企服应用市场:ToB评测及商务社交产业平台

标题: SpringBoot3集成Redis [打印本页]

作者: 郭卫东    时间: 2023-8-28 12:06
标题: SpringBoot3集成Redis
目录

标签:Redis.Mybatis.Lock;
一、简介

缓存在项目开发中,基本上是必选组件之一,Redis作为一个key-value存储系统,具备极高的数据读写效率,并且支持的数据类型比较丰富,在业务场景中的应用非常广泛;
Redis典型的应用场景就是数据缓存能力,用来解决业务中最容易出现的查询性能问题,提升系统的响应效率;其次就是分布式锁机制,用来解决分布式系统中多线程并发处理资源的安全问题;
二、工程搭建

1、工程结构


2、依赖管理

Redis的客户端使用的是lettuce组件的6.2.4.RELEASE版本,该组件具备可扩展和线程安全的特性,支持Redis各种高级功能,如哨兵,集群,流水线,自动重新连接等;
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4.     <version>${spring-boot.version}</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.springframework.integration</groupId>
  8.     <artifactId>spring-integration-redis</artifactId>
  9.     <version>${spring-integration.version}</version>
  10. </dependency>
复制代码
3、Redis配置

配置文件,在lettuce组件中默认使用的是common-pool2连接池组件,在配置中定义几个常用的核心参数;
  1. spring:
  2.   # Redis配置
  3.   data:
  4.     redis:
  5.       host: 127.0.0.1
  6.       port: 6379
  7.       database: 1
  8.       # 读超时
  9.       timeout: 3000
  10.       # 连接超时
  11.       connectTimeout: 5000
  12.       # Lettuce连接池
  13.       lettuce:
  14.         pool:
  15.           # 最小空闲连接
  16.           min-idle: 5
  17.           # 最大空闲连接
  18.           max-idle: 10
  19.           # 最大连接数
  20.           max-active: 100
  21.           # 连接分配应该阻塞的最大时间
  22.           max-wait: 2000
复制代码
配置类
  1. @Configuration
  2. public class RedisConfig {
  3.     /**
  4.      * RedisTemplate模板
  5.      */
  6.     @Bean("redisTemplate")
  7.     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  8.         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  9.         redisTemplate.setConnectionFactory(factory);
  10.         redisTemplate.setKeySerializer(new StringRedisSerializer());
  11.         return redisTemplate;
  12.     }
  13.     /**
  14.      * StringRedisTemplate模板
  15.      */
  16.     @Bean
  17.     public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
  18.         StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
  19.         stringRedisTemplate.setConnectionFactory(factory);
  20.         stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
  21.         return stringRedisTemplate;
  22.     }
  23.     /**
  24.      * Redis分布式锁
  25.      */
  26.     @Bean
  27.     public RedisLockRegistry redisLockRegistry(RedisConnectionFactory factory){
  28.         // 1、锁的密钥前缀:REDIS-LOCK
  29.         // 2、锁的过期时间:20秒
  30.         return new RedisLockRegistry(factory, "REDIS-LOCK",20000L);
  31.     }
  32. }
复制代码
三、Redis用法

1、环境搭建

Redis文档中推荐使用brew工具安装的方式,本地环境中是4.1版本,可以搜索到redis@6.2,即安装该版本,并且可以使用brew命令管理Redis的启动和停止;
  1. brew --version
  2. Homebrew 4.1.0
  3. brew search redis
  4. ==> Formulae
  5. redis             redis-leveldb     redis@3.2         redis@4.0         redis@6.2
  6. brew install redis@6.2
  7. brew services start redis@6.2
  8. ==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2)
  9. brew services restart redis@6.2
  10. ==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)
  11. ==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2)
  12. brew services stop redis@6.2
  13. ==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)
复制代码
配置Redis环境变量,这样可以在命令行使用Redis原生的指令进行启动和客户端操作,在测试完成之后,Mac系统本地的redis@6.2环境就配置好了;
  1. open -e ~/.bash_profile
  2. export REDIS_HOME=/usr/local/opt/redis@6.2
  3. export PATH=$REDIS_HOME/bin:$PATH
  4. source ~/.bash_profile
  5. redis-server
  6. Redis 6.2.13 ,Running in standalone mode , Port: 6379 , PID: 7966
  7. redis-cli
  8. 127.0.0.1:6379> select 1
  9. OK
  10. 127.0.0.1:6379> set redis-key redis-value
  11. OK
  12. 127.0.0.1:6379> get redis-key
  13. "redis-value"
  14. 127.0.0.1:6379> del redis-key
  15. (integer) 1
复制代码
2、数据类型

Strings:字符串是最基本的数据类型,表示一个字节序列,也是最常使用的类型,单个最大能够存储512MB;
  1. public void dataString () throws Exception {
  2.     // 1、添加K-V缓存
  3.     redisTemplate.opsForValue().set("key-01","value-01",300, TimeUnit.SECONDS);
  4.     // 2、添加JSON格式
  5.     JsonMapper jsonMapper = new JsonMapper() ;
  6.     String jsonVar = jsonMapper.writeValueAsString(new IdValue(1,"Json格式")) ;
  7.     redisTemplate.opsForValue().set("key-02",jsonVar,500,TimeUnit.SECONDS);
  8.     Object key01 = redisTemplate.opsForValue().get("key-01");
  9.     Object key02 = redisTemplate.opsForValue().get("key-02");
  10.     log.info("key01:{},key02:{}",key01,key02);
  11. }
复制代码
Lists:链表结构,可以有序的存储多个字符串值,基于数据左写入右读取的方式可以模拟消息队列,单个最大长度为2^32-1;
  1. public void dataList (){
  2.     // 1、左侧写数据
  3.     redisTemplate.opsForList().leftPushAll("data-list","value-01","value-02","value-03");
  4.     // 2、右侧读数据
  5.     Object rightPop01 = redisTemplate.opsForList().rightPop("data-list") ;
  6.     Object rightPop02 = redisTemplate.opsForList().rightPop("data-list") ;
  7.     log.info("rightPop01:{},rightPop02:{}",rightPop01,rightPop02);
  8. }
复制代码
Sets:用来存储无序的、唯一的字符串集合,通常用来跟踪分析唯一项的业务场景,比如唯一标签下的用户群,独立IP等;
  1. public void dataSet (){
  2.     // 1、写数据
  3.     redisTemplate.opsForSet().add("data-set","Java","C++","Python","C++");
  4.     // 2、读数据
  5.     Object pop01 = redisTemplate.opsForSet().pop("data-set");
  6.     Object pop02 = redisTemplate.opsForSet().pop("data-set");
  7.     log.info("pop01:{},pop02:{}",pop01,pop02);
  8. }
复制代码
Hashes:Redis本身的K-V结构上,在value中可以再次使用K-V数据类型,在涉及大规模的关系型数据存储时,会优先使用该类型;
  1. public void dataHash (){
  2.     // 1、写数据
  3.     HashMap<String,String> hashMap = new HashMap<>() ;
  4.     hashMap.put("key1","value1") ;
  5.     hashMap.put("key2","value2") ;
  6.     redisTemplate.opsForHash().putAll("data-hash",hashMap);
  7.     // 2、读数据
  8.     Object kv1 = redisTemplate.opsForHash().get("data-hash","key1");
  9.     Object kv2 = redisTemplate.opsForHash().get("data-hash","key2");
  10.     log.info("kv1:{},kv2:{}",kv1,kv2);
  11. }
复制代码
Sorted-sets:唯一的字符串集合,但是可以通过每个字符串的相关分数来维持顺序,这里的分数可以表示排行顺序,也可以标识为对象的权重或优先级;
  1. public void dataSortedSet (){
  2.     // 1、写数据
  3.     redisTemplate.opsForZSet().add("sorted-set","Java",1.0);
  4.     redisTemplate.opsForZSet().add("sorted-set","Python",3.0);
  5.     redisTemplate.opsForZSet().add("sorted-set","C++",2.0);
  6.     // 2、读数据
  7.     Object popMax = redisTemplate.opsForZSet().popMax("sorted-set");
  8.     Object popMin = redisTemplate.opsForZSet().popMin("sorted-set");
  9.     log.info("popMax:{},popMin:{}",popMax,popMin);
  10. }
复制代码
3、加锁机制

使用SETNX方法即代码中的setIfAbsent,如果key不存在,则设置key保存字符串的值,也就相当于SET,当key已经存在时,不执行任何操作;
  1. @Service
  2. public class RedisLockService {
  3.     @Resource
  4.     private RedisTemplate<String,Object> redisTemplate ;
  5.     public Boolean lock (String lock,long timeout){
  6.         return redisTemplate.opsForValue().setIfAbsent(lock,lock,timeout, TimeUnit.SECONDS);
  7.     }
  8.     public void unLock (String lock){
  9.         Object lockVal = redisTemplate.opsForValue().get(lock);
  10.         if (!Objects.isNull(lockVal) && Objects.equals(lockVal.toString(),lock)){
  11.             redisTemplate.delete(lock) ;
  12.         }
  13.     }
  14. }
复制代码
RedisLockRegistry是由spring-integration组件提供的封装类,基于Redis实现分布式锁的能力;有关于两种锁机制的测试,可以查看代码工程中RedisLockTest单元测试类,
  1. @Service
  2. public class LockRegistryService {
  3.     @Resource
  4.     protected RedisLockRegistry redisLockRegistry;
  5.     /**
  6.      * 尝试一次加锁
  7.      */
  8.     @SneakyThrows
  9.     public <T> Boolean tryLock(T lockKey, Long time) {
  10.         return redisLockRegistry.obtain(lockKey).tryLock(time, TimeUnit.SECONDS);
  11.     }
  12.     /**
  13.      * 重试机制多次加锁
  14.      */
  15.     @SneakyThrows
  16.     public <T> Boolean reTryLock(T lockKey, Long time,int retryNum) {
  17.         Boolean lockFlag = tryLock(lockKey, time);
  18.         if (Boolean.TRUE.equals(lockFlag)) {
  19.             return Boolean.TRUE;
  20.         }
  21.         for (int i = 0; i < retryNum; i++) {
  22.             if (Boolean.TRUE.equals(tryLock(lockKey, time))){
  23.                 return Boolean.TRUE;
  24.             }
  25.         }
  26.         return Boolean.FALSE;
  27.     }
  28.     /**
  29.      * 释放锁
  30.      */
  31.     public <T> void unlock(T lockKey) {
  32.         redisLockRegistry.obtain(lockKey).unlock();
  33.     }
  34. }
复制代码
四、Mybatis缓存

1、基础配置

在Mybatis框架中,一级缓存是指SqlSession级别内;二级缓存是指Mapper级别内,需要在配置文件中开启,二级缓存并不适用于频繁修改数据的业务场景;
配置文件
  1. mybatis-plus:
  2.   configuration:
  3.     cache-enabled: true
  4.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码
Mapper注解,这里使用自定义的MybatisCache类,并且使用Redis组件来缓存数据;
  1. @CacheNamespace(implementation = MybatisCache.class)
  2. public interface ArticleMapper extends BaseMapper<Article> {
  3. }
复制代码
2、自定义实现

通过实现Cache接口,来定义二级缓存的处理策略,可以运行MybatisTest测试类中的方法,分析其执行的原理,符合Cache-Aside模式,即先查询缓存,如果未命中就查询数据库,并将结果写入缓存,数据更新时会删除相关缓存;
  1. public class MybatisCache implements Cache {
  2.     private static final Logger log = LoggerFactory.getLogger(MybatisCache.class);
  3.     private RedisTemplate<String,Object> redisTemplate ;
  4.     private String id ;
  5.     public MybatisCache (String id){
  6.         log.info("mybatis...cache...id:{}",id);
  7.         this.id = id ;
  8.     }
  9.     public RedisTemplate<String,Object> getRedisTemplate(){
  10.         if(redisTemplate == null) {
  11.             synchronized (MybatisCache.class) {
  12.                 if(redisTemplate == null) {
  13.                     redisTemplate = SpringContextUtil.getBean("redisTemplate",RedisTemplate.class);
  14.                     return redisTemplate;
  15.                 }
  16.                 return redisTemplate;
  17.             }
  18.         }
  19.         return redisTemplate ;
  20.     }
  21.     @Override
  22.     public String getId() {
  23.         return this.id ;
  24.     }
  25.     @Override
  26.     public void putObject(Object key, Object value) {
  27.         log.info("mybatis...cache...put...key:{},value:{}",key,value);
  28.         this.getRedisTemplate().opsForValue().set(String.valueOf(key),value,5, TimeUnit.MINUTES);
  29.     }
  30.     @Override
  31.     public Object getObject(Object key) {
  32.         log.info("mybatis...cache...get...key:{}",key);
  33.         return this.getRedisTemplate().opsForValue().get(String.valueOf(key)) ;
  34.     }
  35.     @Override
  36.     public Object removeObject(Object key) {
  37.         log.info("mybatis...cache...remove...key:{}",key);
  38.         return this.getRedisTemplate().delete(String.valueOf(key));
  39.     }
  40.     @Override
  41.     public void clear() {
  42.         Set<String> keys = this.getRedisTemplate().keys("*" + id + "*");
  43.         if (keys != null && keys.size()>0){
  44.             log.info("mybatis...cache...clear...keys:{}",keys);
  45.             this.getRedisTemplate().delete(keys) ;
  46.         }
  47.     }
  48.     @Override
  49.     public int getSize() {
  50.         Set<String> keys = this.getRedisTemplate().keys("*" + id + "*");
  51.         if (keys != null){
  52.             log.info("mybatis...cache...size...keys:{}",keys.size());
  53.             return keys.size() ;
  54.         }
  55.         return 0;
  56.     }
  57. }
复制代码
五、参考源码
  1. 文档仓库:
  2. https://gitee.com/cicadasmile/butte-java-note
  3. 源码仓库:
  4. https://gitee.com/cicadasmile/butte-spring-parent
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4