SpringBoot之集成Redis

打印 上一主题 下一主题

主题 564|帖子 564|积分 1692

一、Redis集成简介

Redis是我们Java开发中,利用频次非常高的一个nosql数据库,数据以key-value键值对的形式存储在内存中。redis的常用利用场景,可以做缓存,分布式锁,自增序列等,利用redis的方式和我们利用数据库的方式差不多,起首我们要在自己的本机电脑或者服务器上安装一个redis的服务器,通过我们的java客户端在步伐中进行集成,然后通过客户端完成对redis的增删改查操作。
redis的Java客户端范例照旧很多的,常见的有jedis, redission,lettuce等,
以是我们在集成的时候,我们可以选择直接集成这些原生客户端。
但是在springBoot中更常见的方式是集成spring-data-redis,这是spring提供的一个专门用来操作redis的项目,封装了对redis的常用操作,里边主要封装了jedis和lettuce两个客户端。相称于是在他们的基础上加了一层门面。
二、集成步调

2.1 添加依靠

添加redis所需依靠:(spring-boot-starter-data-redis 默认利用的就是lettuce这个客户端)
  1. <!-- 集成redis依赖  -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
复制代码
完备pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <groupId>com.lsqingfeng.springboot</groupId>
  7.     <artifactId>springboot-learning</artifactId>
  8.     <version>1.0.0</version>
  9.     <properties>
  10.         <maven.compiler.source>8</maven.compiler.source>
  11.         <maven.compiler.target>8</maven.compiler.target>
  12.     </properties>
  13.     <dependencyManagement>
  14.         <dependencies>
  15.             <dependency>
  16.                 <groupId>org.springframework.boot</groupId>
  17.                 <artifactId>spring-boot-dependencies</artifactId>
  18.                 <version>2.6.2</version>
  19.                 <type>pom</type>
  20.                 <scope>import</scope>
  21.             </dependency>
  22.         </dependencies>
  23.     </dependencyManagement>
  24.     <dependencies>
  25.         <dependency>
  26.             <groupId>org.springframework.boot</groupId>
  27.             <artifactId>spring-boot-starter-web</artifactId>
  28.         </dependency>
  29.         <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
  30.         <dependency>
  31.             <groupId>org.projectlombok</groupId>
  32.             <artifactId>lombok</artifactId>
  33.             <version>1.18.22</version>
  34.             <scope>provided</scope>
  35.         </dependency>
  36.         <!-- mybatis-plus 所需依赖  -->
  37.         <dependency>
  38.             <groupId>com.baomidou</groupId>
  39.             <artifactId>mybatis-plus-boot-starter</artifactId>
  40.             <version>3.5.1</version>
  41.         </dependency>
  42.         <dependency>
  43.             <groupId>com.baomidou</groupId>
  44.             <artifactId>mybatis-plus-generator</artifactId>
  45.             <version>3.5.1</version>
  46.         </dependency>
  47.         <dependency>
  48.             <groupId>org.freemarker</groupId>
  49.             <artifactId>freemarker</artifactId>
  50.             <version>2.3.31</version>
  51.         </dependency>
  52.         <!-- 开发热启动 -->
  53.         <dependency>
  54.             <groupId>org.springframework.boot</groupId>
  55.             <artifactId>spring-boot-devtools</artifactId>
  56.             <optional>true</optional>
  57.         </dependency>
  58.         <!-- MySQL连接 -->
  59.         <dependency>
  60.             <groupId>mysql</groupId>
  61.             <artifactId>mysql-connector-java</artifactId>
  62.             <scope>runtime</scope>
  63.         </dependency>
  64.         <!-- 集成redis依赖  -->
  65.         <dependency>
  66.             <groupId>org.springframework.boot</groupId>
  67.             <artifactId>spring-boot-starter-data-redis</artifactId>
  68.         </dependency>
  69.     </dependencies>
  70. </project>
复制代码
注意点:如果我们想要利用jedis客户端怎么办呢?就必要排除lettuce这个依靠,再引入jedis的相干依靠就可以了。
  1. <dependency>  
  2.     <groupId>org.springframework.boot</groupId>  
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>  
  4.     <exclusions>  
  5.         <exclusion>  
  6.             <groupId>io.lettuce</groupId>  
  7.             <artifactId>lettuce-core</artifactId>  
  8.         </exclusion>  
  9.     </exclusions>  
  10. </dependency>
  11. <dependency>  
  12.     <groupId>redis.clients</groupId>  
  13.     <artifactId>jedis</artifactId>  
  14.     <version>你的Jedis版本号</version>  
  15. </dependency>
复制代码
两者的区别
Lettuce更适合必要异步处理、线程安全以及支持哨兵和集群模式的场景(线程安全);
而Jedis则更适合简单的同步操作,以及在不必要哨兵和集群模式的场景中利用(线程不安全)。
2.2 添加配置

然后我们必要配置连接redis所需的账号暗码等信息,这里大家要提前安装好redis,保证我们的本机步伐可以连接到我们的redis, 如果不知道redis如何安装,可以参考文章: [Linux系统安装redis6.0.5] blog.csdn.net/lsqingfeng/…
常规配置如下: 在application.yml配置文件中配置 redis的连接信息
  1. spring:
  2.   redis:
  3.     host: localhost
  4.     port: 6379
  5.     password: 123456
  6.     database: 0
复制代码
如果有其他配置放到一起:
  1. server:
  2.   port: 19191
  3. spring:
  4.   datasource:
  5.     driver-class-name: com.mysql.cj.jdbc.Driver
  6.     url: jdbc:mysql://localhost:3306/springboot_learning?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
  7.     username: root
  8.     password: root
  9.   redis:
  10.     host: localhost
  11.     port: 6379
  12.     password: 123456
  13.     database: 0
  14.     lettuce:
  15.       pool:
  16.         max-idle: 16
  17.         max-active: 32
  18.         min-idle: 8
  19.   devtools:
  20.     restart:
  21.       enable: true
  22. third:
  23.   weather:
  24.     url: http://www.baidu.com
  25.     port: 8080
  26.     username: test
  27.     cities:
  28.       - 北京
  29.       - 上海
  30.       - 广州
  31.     list[0]: aaa
  32.     list[1]: bbb
  33.     list[2]: ccc
复制代码
这样我们就可以直接在项目当中操作redis了。如果利用的是集群,那么利用如下配置方式:
  1. spring:
  2.   redis:
  3.     password: 123456
  4.     cluster:
  5.       nodes: 10.255.144.115:7001,10.255.144.115:7002,10.255.144.115:7003,10.255.144.115:7004,10.255.144.115:7005,10.255.144.115:7006
  6.       max-redirects: 3
复制代码
但是有的时候我们想要给我们的redis客户端配置上连接池。
就像我们连接mysql的时候,也会配置连接池一样,目标就是增加对于数据连接的管理,提升访问的效率,也保证了对资源的合理利用。那么我们如何配置连接池呢,这里大家肯定要注意了,很多网上的文章中,介绍的方法可能由于版本太低,都不是特别的准确。
比如很多人利用spring.redis.pool来配置,这个是不对的(不清楚是不是老版本是这样的配置的,但是在springboot-starter-data-redis中这种写法不对)。起首是配置文件,由于我们利用的lettuce客户端,以是配置的时候,在spring.redis下加上lettuce再加上pool来配置,详细如下;
  1. spring:
  2.   redis:
  3.     host: 10.255.144.111
  4.     port: 6379
  5.     password: 123456
  6.     database: 0
  7.     lettuce:
  8.       pool:
  9.         max-idle: 16
  10.         max-active: 32
  11.         min-idle: 8
复制代码
如果利用的是jedis,就把lettuce换成jedis(同时要注意依靠也是要换的)。
但是仅仅这在配置文件中参加,实在连接池是不会生效的。这里大家肯定要注意,很多同砚在配置文件上加上了这段就以为连接池已经配置好了,实在并没有,还少了最关键的一步,就是要导入一个依靠,不导入的话,这么配置也没有用。
  1. <dependency>
  2.     <groupId>org.apache.commons</groupId>
  3.     <artifactId>commons-pool2</artifactId>
  4. </dependency>
复制代码
之后,连接池才会生效。我们可以做一个对比。 在导包前后,观察RedisTemplate对象的值就可以看出来。
导入之前:

导入之后:

导入之后,我们的连接池信息才有值,这也印证了我们上面的结论。
详细的配置信息我们可以看一下源代码,源码中利用RedisProperties 这个类来接收redis的配置参数。

2.3 项目中利用

我们的配置工作准备就绪以后,我们就可以在项目中操作redis了,操作的话,利用spring-data-redis中为我们提供的 RedisTemplate 这个类,就可以操作了。我们先举个简单的例子,插入一个键值对(值为string)。
  1. package com.lsqingfeng.springboot.controller;
  2. import com.lsqingfeng.springboot.base.Result;
  3. import org.springframework.data.redis.core.RedisTemplate;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. /**
  8. * @className: RedisController
  9. * @description:
  10. * @author: sh.Liu
  11. * @date: 2022-03-08 14:28
  12. */
  13. @RestController
  14. @RequestMapping("redis")
  15. public class RedisController {
  16.     private final RedisTemplate redisTemplate;
  17.     public RedisController(RedisTemplate redisTemplate) {
  18.         this.redisTemplate = redisTemplate;
  19.     }
  20.     @GetMapping("save")
  21.     public Result save(String key, String value){
  22.         redisTemplate.opsForValue().set(key, value);
  23.         return Result.success();
  24.     }
  25. }
复制代码
三、工具类封装

  1. package com.lsqingfeng.springboot.utils;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.RedisTemplate;
  4. import org.springframework.stereotype.Component;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Set;
  8. import java.util.concurrent.TimeUnit;
  9. /**
  10. * @className: RedisUtil
  11. * @description:
  12. * @author: sh.Liu
  13. * @date: 2022-03-09 14:07
  14. */
  15. @Component
  16. public class RedisUtil {
  17.     @Autowired
  18.     private RedisTemplate redisTemplate;
  19.     /**
  20.      * 给一个指定的 key 值附加过期时间
  21.      *
  22.      * @param key
  23.      * @param time
  24.      * @return
  25.      */
  26.     public boolean expire(String key, long time) {
  27.         return redisTemplate.expire(key, time, TimeUnit.SECONDS);
  28.     }
  29.     /**
  30.      * 根据key 获取过期时间
  31.      *
  32.      * @param key
  33.      * @return
  34.      */
  35.     public long getTime(String key) {
  36.         return redisTemplate.getExpire(key, TimeUnit.SECONDS);
  37.     }
  38.     /**
  39.      * 根据key 获取过期时间
  40.      *
  41.      * @param key
  42.      * @return
  43.      */
  44.     public boolean hasKey(String key) {
  45.         return redisTemplate.hasKey(key);
  46.     }
  47.     /**
  48.      * 移除指定key 的过期时间
  49.      *
  50.      * @param key
  51.      * @return
  52.      */
  53.     public boolean persist(String key) {
  54.         return redisTemplate.boundValueOps(key).persist();
  55.     }
  56.     //- - - - - - - - - - - - - - - - - - - - -  String类型 - - - - - - - - - - - - - - - - - - - -
  57.     /**
  58.      * 根据key获取值
  59.      *
  60.      * @param key 键
  61.      * @return 值
  62.      */
  63.     public Object get(String key) {
  64.         return key == null ? null : redisTemplate.opsForValue().get(key);
  65.     }
  66.     /**
  67.      * 将值放入缓存
  68.      *
  69.      * @param key   键
  70.      * @param value 值
  71.      * @return true成功 false 失败
  72.      */
  73.     public void set(String key, String value) {
  74.         redisTemplate.opsForValue().set(key, value);
  75.     }
  76.     /**
  77.      * 将值放入缓存并设置时间
  78.      *
  79.      * @param key   键
  80.      * @param value 值
  81.      * @param time  时间(秒) -1为无期限
  82.      * @return true成功 false 失败
  83.      */
  84.     public void set(String key, String value, long time) {
  85.         if (time > 0) {
  86.             redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
  87.         } else {
  88.             redisTemplate.opsForValue().set(key, value);
  89.         }
  90.     }
  91.     /**
  92.      * 批量添加 key (重复的键会覆盖)
  93.      *
  94.      * @param keyAndValue
  95.      */
  96.     public void batchSet(Map<String, String> keyAndValue) {
  97.         redisTemplate.opsForValue().multiSet(keyAndValue);
  98.     }
  99.     /**
  100.      * 批量添加 key-value 只有在键不存在时,才添加
  101.      * map 中只要有一个key存在,则全部不添加
  102.      *
  103.      * @param keyAndValue
  104.      */
  105.     public void batchSetIfAbsent(Map<String, String> keyAndValue) {
  106.         redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
  107.     }
  108.     /**
  109.      * 对一个 key-value 的值进行加减操作,
  110.      * 如果该 key 不存在 将创建一个key 并赋值该 number
  111.      * 如果 key 存在,但 value 不是长整型 ,将报错
  112.      *
  113.      * @param key
  114.      * @param number
  115.      */
  116.     public Long increment(String key, long number) {
  117.         return redisTemplate.opsForValue().increment(key, number);
  118.     }
  119.     /**
  120.      * 对一个 key-value 的值进行加减操作,
  121.      * 如果该 key 不存在 将创建一个key 并赋值该 number
  122.      * 如果 key 存在,但 value 不是 纯数字 ,将报错
  123.      *
  124.      * @param key
  125.      * @param number
  126.      */
  127.     public Double increment(String key, double number) {
  128.         return redisTemplate.opsForValue().increment(key, number);
  129.     }
  130.     //- - - - - - - - - - - - - - - - - - - - -  set类型 - - - - - - - - - - - - - - - - - - - -
  131.     /**
  132.      * 将数据放入set缓存
  133.      *
  134.      * @param key 键
  135.      * @return
  136.      */
  137.     public void sSet(String key, String value) {
  138.         redisTemplate.opsForSet().add(key, value);
  139.     }
  140.     /**
  141.      * 获取变量中的值
  142.      *
  143.      * @param key 键
  144.      * @return
  145.      */
  146.     public Set<Object> members(String key) {
  147.         return redisTemplate.opsForSet().members(key);
  148.     }
  149.     /**
  150.      * 随机获取变量中指定个数的元素
  151.      *
  152.      * @param key   键
  153.      * @param count 值
  154.      * @return
  155.      */
  156.     public void randomMembers(String key, long count) {
  157.         redisTemplate.opsForSet().randomMembers(key, count);
  158.     }
  159.     /**
  160.      * 随机获取变量中的元素
  161.      *
  162.      * @param key 键
  163.      * @return
  164.      */
  165.     public Object randomMember(String key) {
  166.         return redisTemplate.opsForSet().randomMember(key);
  167.     }
  168.     /**
  169.      * 弹出变量中的元素
  170.      *
  171.      * @param key 键
  172.      * @return
  173.      */
  174.     public Object pop(String key) {
  175.         return redisTemplate.opsForSet().pop("setValue");
  176.     }
  177.     /**
  178.      * 获取变量中值的长度
  179.      *
  180.      * @param key 键
  181.      * @return
  182.      */
  183.     public long size(String key) {
  184.         return redisTemplate.opsForSet().size(key);
  185.     }
  186.     /**
  187.      * 根据value从一个set中查询,是否存在
  188.      *
  189.      * @param key   键
  190.      * @param value 值
  191.      * @return true 存在 false不存在
  192.      */
  193.     public boolean sHasKey(String key, Object value) {
  194.         return redisTemplate.opsForSet().isMember(key, value);
  195.     }
  196.     /**
  197.      * 检查给定的元素是否在变量中。
  198.      *
  199.      * @param key 键
  200.      * @param obj 元素对象
  201.      * @return
  202.      */
  203.     public boolean isMember(String key, Object obj) {
  204.         return redisTemplate.opsForSet().isMember(key, obj);
  205.     }
  206.     /**
  207.      * 转移变量的元素值到目的变量。
  208.      *
  209.      * @param key     键
  210.      * @param value   元素对象
  211.      * @param destKey 元素对象
  212.      * @return
  213.      */
  214.     public boolean move(String key, String value, String destKey) {
  215.         return redisTemplate.opsForSet().move(key, value, destKey);
  216.     }
  217.     /**
  218.      * 批量移除set缓存中元素
  219.      *
  220.      * @param key    键
  221.      * @param values 值
  222.      * @return
  223.      */
  224.     public void remove(String key, Object... values) {
  225.         redisTemplate.opsForSet().remove(key, values);
  226.     }
  227.     /**
  228.      * 通过给定的key求2个set变量的差值
  229.      *
  230.      * @param key     键
  231.      * @param destKey 键
  232.      * @return
  233.      */
  234.     public Set<Set> difference(String key, String destKey) {
  235.         return redisTemplate.opsForSet().difference(key, destKey);
  236.     }
  237.     //- - - - - - - - - - - - - - - - - - - - -  hash类型 - - - - - - - - - - - - - - - - - - - -
  238.     /**
  239.      * 加入缓存
  240.      *
  241.      * @param key 键
  242.      * @param map 键
  243.      * @return
  244.      */
  245.     public void add(String key, Map<String, String> map) {
  246.         redisTemplate.opsForHash().putAll(key, map);
  247.     }
  248.     /**
  249.      * 获取 key 下的 所有  hashkey 和 value
  250.      *
  251.      * @param key 键
  252.      * @return
  253.      */
  254.     public Map<Object, Object> getHashEntries(String key) {
  255.         return redisTemplate.opsForHash().entries(key);
  256.     }
  257.     /**
  258.      * 验证指定 key 下 有没有指定的 hashkey
  259.      *
  260.      * @param key
  261.      * @param hashKey
  262.      * @return
  263.      */
  264.     public boolean hashKey(String key, String hashKey) {
  265.         return redisTemplate.opsForHash().hasKey(key, hashKey);
  266.     }
  267.     /**
  268.      * 获取指定key的值string
  269.      *
  270.      * @param key  键
  271.      * @param key2 键
  272.      * @return
  273.      */
  274.     public String getMapString(String key, String key2) {
  275.         return redisTemplate.opsForHash().get("map1", "key1").toString();
  276.     }
  277.     /**
  278.      * 获取指定的值Int
  279.      *
  280.      * @param key  键
  281.      * @param key2 键
  282.      * @return
  283.      */
  284.     public Integer getMapInt(String key, String key2) {
  285.         return (Integer) redisTemplate.opsForHash().get("map1", "key1");
  286.     }
  287.     /**
  288.      * 弹出元素并删除
  289.      *
  290.      * @param key 键
  291.      * @return
  292.      */
  293.     public String popValue(String key) {
  294.         return redisTemplate.opsForSet().pop(key).toString();
  295.     }
  296.     /**
  297.      * 删除指定 hash 的 HashKey
  298.      *
  299.      * @param key
  300.      * @param hashKeys
  301.      * @return 删除成功的 数量
  302.      */
  303.     public Long delete(String key, String... hashKeys) {
  304.         return redisTemplate.opsForHash().delete(key, hashKeys);
  305.     }
  306.     /**
  307.      * 给指定 hash 的 hashkey 做增减操作
  308.      *
  309.      * @param key
  310.      * @param hashKey
  311.      * @param number
  312.      * @return
  313.      */
  314.     public Long increment(String key, String hashKey, long number) {
  315.         return redisTemplate.opsForHash().increment(key, hashKey, number);
  316.     }
  317.     /**
  318.      * 给指定 hash 的 hashkey 做增减操作
  319.      *
  320.      * @param key
  321.      * @param hashKey
  322.      * @param number
  323.      * @return
  324.      */
  325.     public Double increment(String key, String hashKey, Double number) {
  326.         return redisTemplate.opsForHash().increment(key, hashKey, number);
  327.     }
  328.     /**
  329.      * 获取 key 下的 所有 hashkey 字段
  330.      *
  331.      * @param key
  332.      * @return
  333.      */
  334.     public Set<Object> hashKeys(String key) {
  335.         return redisTemplate.opsForHash().keys(key);
  336.     }
  337.     /**
  338.      * 获取指定 hash 下面的 键值对 数量
  339.      *
  340.      * @param key
  341.      * @return
  342.      */
  343.     public Long hashSize(String key) {
  344.         return redisTemplate.opsForHash().size(key);
  345.     }
  346.     //- - - - - - - - - - - - - - - - - - - - -  list类型 - - - - - - - - - - - - - - - - - - - -
  347.     /**
  348.      * 在变量左边添加元素值
  349.      *
  350.      * @param key
  351.      * @param value
  352.      * @return
  353.      */
  354.     public void leftPush(String key, Object value) {
  355.         redisTemplate.opsForList().leftPush(key, value);
  356.     }
  357.     /**
  358.      * 获取集合指定位置的值。
  359.      *
  360.      * @param key
  361.      * @param index
  362.      * @return
  363.      */
  364.     public Object index(String key, long index) {
  365.         return redisTemplate.opsForList().index("list", 1);
  366.     }
  367.     /**
  368.      * 获取指定区间的值。
  369.      *
  370.      * @param key
  371.      * @param start
  372.      * @param end
  373.      * @return
  374.      */
  375.     public List<Object> range(String key, long start, long end) {
  376.         return redisTemplate.opsForList().range(key, start, end);
  377.     }
  378.     /**
  379.      * 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
  380.      * 如果中间参数值存在的话。
  381.      *
  382.      * @param key
  383.      * @param pivot
  384.      * @param value
  385.      * @return
  386.      */
  387.     public void leftPush(String key, String pivot, String value) {
  388.         redisTemplate.opsForList().leftPush(key, pivot, value);
  389.     }
  390.     /**
  391.      * 向左边批量添加参数元素。
  392.      *
  393.      * @param key
  394.      * @param values
  395.      * @return
  396.      */
  397.     public void leftPushAll(String key, String... values) {
  398. //        redisTemplate.opsForList().leftPushAll(key,"w","x","y");
  399.         redisTemplate.opsForList().leftPushAll(key, values);
  400.     }
  401.     /**
  402.      * 向集合最右边添加元素。
  403.      *
  404.      * @param key
  405.      * @param value
  406.      * @return
  407.      */
  408.     public void leftPushAll(String key, String value) {
  409.         redisTemplate.opsForList().rightPush(key, value);
  410.     }
  411.     /**
  412.      * 向左边批量添加参数元素。
  413.      *
  414.      * @param key
  415.      * @param values
  416.      * @return
  417.      */
  418.     public void rightPushAll(String key, String... values) {
  419.         //redisTemplate.opsForList().leftPushAll(key,"w","x","y");
  420.         redisTemplate.opsForList().rightPushAll(key, values);
  421.     }
  422.     /**
  423.      * 向已存在的集合中添加元素。
  424.      *
  425.      * @param key
  426.      * @param value
  427.      * @return
  428.      */
  429.     public void rightPushIfPresent(String key, Object value) {
  430.         redisTemplate.opsForList().rightPushIfPresent(key, value);
  431.     }
  432.     /**
  433.      * 向已存在的集合中添加元素。
  434.      *
  435.      * @param key
  436.      * @return
  437.      */
  438.     public long listLength(String key) {
  439.         return redisTemplate.opsForList().size(key);
  440.     }
  441.     /**
  442.      * 移除集合中的左边第一个元素。
  443.      *
  444.      * @param key
  445.      * @return
  446.      */
  447.     public void leftPop(String key) {
  448.         redisTemplate.opsForList().leftPop(key);
  449.     }
  450.     /**
  451.      * 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
  452.      *
  453.      * @param key
  454.      * @return
  455.      */
  456.     public void leftPop(String key, long timeout, TimeUnit unit) {
  457.         redisTemplate.opsForList().leftPop(key, timeout, unit);
  458.     }
  459.     /**
  460.      * 移除集合中右边的元素。
  461.      *
  462.      * @param key
  463.      * @return
  464.      */
  465.     public void rightPop(String key) {
  466.         redisTemplate.opsForList().rightPop(key);
  467.     }
  468.     /**
  469.      * 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
  470.      *
  471.      * @param key
  472.      * @return
  473.      */
  474.     public void rightPop(String key, long timeout, TimeUnit unit) {
  475.         redisTemplate.opsForList().rightPop(key, timeout, unit);
  476.     }
  477. }
复制代码
四、序列化 (正常都必要自定义序列化)

Redis本身提供了一下一种序列化的方式:


  • GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
  • Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer现实上是一样的
  • JacksonJsonRedisSerializer: 序列化object对象为json字符串
  • JdkSerializationRedisSerializer: 序列化java对象
  • StringRedisSerializer: 简单的字符串序列化
如果我们存储的是String范例默认利用的是StringRedisSerializer 这种序列化方式。
如果我们存储的是对象默认利用的是 JdkSerializationRedisSerializer,也就是Jdk的序列化方式(通过ObjectOutputStream和ObjectInputStream实现,缺点是我们无法直观看到存储的对象内容)。
通过观察RedisTemplate的源码我们就可以看出来,默认利用的是JdkSerializationRedisSerializer. 这种序列化最大的题目就是存入对象后,我们很难直观看到存储的内容,很不方便我们排查题目:

而一般我们最经常利用的对象序列化方式是: Jackson2JsonRedisSerializer
设置序列化方式的主要方法就是我们在配置类中,自己来创建RedisTemplate对象,并在创建的过程中指定对应的序列化方式。
  1. @Configuration  
  2. public class RedisConfig {  
  3.     // 定义一个Bean,名称为"redisTemplate",返回类型为RedisTemplate<String, Object>  
  4.     @Bean(name = "redisTemplate")  
  5.     public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {  
  6.         // 创建一个新的RedisTemplate实例,用于操作Redis  
  7.         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();  
  8.         // 设置RedisTemplate使用的连接工厂,以便它能够连接到Redis服务器  
  9.         redisTemplate.setConnectionFactory(factory);  
  10.          
  11.         // 创建一个StringRedisSerializer实例,用于序列化Redis的key为字符串  
  12.         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();  
  13.          
  14.         // 创建一个Jackson2JsonRedisSerializer实例,用于序列化Redis的value为JSON格式  
  15.         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);  
  16.          
  17.         // 创建一个ObjectMapper实例,用于处理JSON的序列化和反序列化  
  18.         ObjectMapper objectMapper = new ObjectMapper();  
  19.          
  20.         // 设置ObjectMapper的属性访问级别,以便能够序列化对象的所有属性  
  21.         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
  22.          
  23.         // 启用默认的类型信息,以便在反序列化时能够知道对象的实际类型  
  24.         // 注意:这里使用了新的方法替换了过期的enableDefaultTyping方法  
  25.         // 方法过期,改为下面代码  
  26.         // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
  27.         objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,  
  28.                 ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);  
  29.          
  30.         // 设置Jackson2JsonRedisSerializer使用的ObjectMapper  
  31.         jackson2JsonRedisSerializer.setObjectMapper(objectMapper);  
  32.   
  33.         // 设置RedisTemplate的key序列化器为stringRedisSerializer  
  34.         redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型  
  35.         // 设置RedisTemplate的value序列化器为jackson2JsonRedisSerializer  
  36.         redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型  
  37.          
  38.         // 设置RedisTemplate的hash key序列化器为stringRedisSerializer  
  39.         redisTemplate.setHashKeySerializer(stringRedisSerializer);  // key的序列化类型  
  40.         // 设置RedisTemplate的hash value序列化器为jackson2JsonRedisSerializer  
  41.         redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);   // value的序列化类型  
  42.         
  43.         // 调用RedisTemplate的afterPropertiesSet方法,该方法会执行一些初始化操作,比如检查序列化器是否设置等  
  44.         redisTemplate.afterPropertiesSet();  
  45.          
  46.         // 返回配置好的RedisTemplate实例  
  47.         return redisTemplate;  
  48.     }  
  49. }
复制代码
这样利用的时候,就会按照我们设置的json序列化方式进行存储,我们也可以在redis中查察内容的时候方便的查察到属性值。
五、分布式锁

(一)RedisTemplate 去实现

场景一:单体应用

单机数据同等性架构如下图所示:多个可客户访问同一个服务器,连接同一个数据库。

场景形貌:客户端模拟购买商品过程,在Redis中设定库存总数剩100个,多个客户端同时并发购买。
  1. @RestController
  2. public class IndexController1 {
  3.     @Autowired
  4.     StringRedisTemplate template;
  5.     @RequestMapping("/buy1")
  6.     public String index(){
  7.         // Redis中存有goods:001号商品,数量为100
  8.         String result = template.opsForValue().get("goods:001");
  9.         // 获取到剩余商品数
  10.         int total = result == null ? 0 : Integer.parseInt(result);
  11.         if( total > 0 ){
  12.             // 剩余商品数大于0 ,则进行扣减
  13.             int realTotal = total -1;
  14.             // 将商品数回写数据库
  15.             template.opsForValue().set("goods:001",String.valueOf(realTotal));
  16.             System.out.println("购买商品成功,库存还剩:"+realTotal +"件, 服务端口为8001");
  17.             return "购买商品成功,库存还剩:"+realTotal +"件, 服务端口为8001";
  18.         }else{
  19.             System.out.println("购买商品失败,服务端口为8001");
  20.         }
  21.         return "购买商品失败,服务端口为8001";
  22.     }
  23. }
复制代码
利用Jmeter模拟高并发场景,测试结果如下

测试结果出现多个用户购买同一商品,发生了数据差别等题目!
办理办法:单体应用的情况下,对并发的操作进行加锁操作,保证对数据的操作具有原子性


  • synchronized
  • ReentrantLock
synchronized (自动获取锁,并在退出时自动开释锁)去实现如下
  1. @RestController  
  2. public class IndexController2 {  
  3.   
  4.     @Autowired  
  5.     StringRedisTemplate template;  
  6.   
  7.     @RequestMapping("/buy2")  
  8.     public synchronized String index() {  
  9.         String result = template.opsForValue().get("goods:001");  
  10.         int total = result == null ? 0 : Integer.parseInt(result);  
  11.         if (total > 0) {  
  12.             int realTotal = total - 1;  
  13.             template.opsForValue().set("goods:001", String.valueOf(realTotal));  
  14.             System.out.println("购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001");  
  15.             return "购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001";  
  16.         } else {  
  17.             System.out.println("购买商品失败,服务端口为8001");  
  18.         }  
  19.         return "购买商品失败,服务端口为8001";  
  20.     }  
  21. }
复制代码
ReentrantLock(必要手动获取锁,并在退出时手动开释锁) 去实现
在针对单体应用时的操作(ReentrantLock去实现相对来说好一点,由于颗粒度更细)
  1. @RestController
  2. public class IndexController2 {
  3.     // 使用ReentrantLock锁解决单体应用的并发问题
  4.     Lock lock = new ReentrantLock();
  5.     @Autowired
  6.     StringRedisTemplate template;
  7.     @RequestMapping("/buy2")
  8.     public String index() {
  9.         lock.lock();
  10.         try {
  11.             String result = template.opsForValue().get("goods:001");
  12.             int total = result == null ? 0 : Integer.parseInt(result);
  13.             if (total > 0) {
  14.                 int realTotal = total - 1;
  15.                 template.opsForValue().set("goods:001", String.valueOf(realTotal));
  16.                 System.out.println("购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001");
  17.                 return "购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001";
  18.             } else {
  19.                 System.out.println("购买商品失败,服务端口为8001");
  20.             }
  21.         } catch (Exception e) {
  22.             lock.unlock();
  23.         } finally {
  24.             lock.unlock();
  25.         }
  26.         return "购买商品失败,服务端口为8001";
  27.     }
  28. }
复制代码
100个商品100个人买最后剩余为0
场景二:分布式架构部署

提供两个服务,端口分别为8001、8002,连接同一个Redis服务,在服务前面有一台Nginx作为负载均衡

两台服务代码相同,只是端口差别
将8001、8002两个服务启动,每个服务依然用ReentrantLock加锁,用Jmeter做并发测试,发现会出现数据同等性题目!

我这边直接写终极版本(存粹的只用redis)
   要求:
1.保证自己加的锁,自己删自己的(由以下的uuid生成value去控制,以防止其他的线程把自己的删除了或者自己删除了别人的)


  

  • REDIS_LOCK: 这是你想要设置的Redis键(Key)。在分布式锁的场景中,它通常是一个唯一的字符串,用于标识某个资源或操作。
  • value: 这是你想要设置的Redis值(Value)。在分布式锁的场景中,这通常是一个表示锁持有者的唯一标识,例如线程ID或进程ID。
  • 10L: 这是锁的过期时间,单位是秒。这意味着如果持有锁的客户端在这个时间内没有开释锁(例如,由于崩溃或网络题目),那么锁将自动过期,其他客户端可以获取它。这是一个重要的安全机制,可以防止死锁。
  • TimeUnit.SECONDS: 这是时间单位。TimeUnit是一个罗列范例,表示时间的单位,如毫秒、秒、分钟等。在这里,我们利用SECONDS表示过期时间是以秒为单位的。
    redis事件或lua脚本(lua脚本的实行是原子的),如下
  1. @RestController
  2. public class IndexController7 {
  3.     public static final String REDIS_LOCK = "lock";
  4.     @Autowired
  5.     StringRedisTemplate template;
  6.     @RequestMapping("/buy7")
  7.     public String index(){
  8.         // 每个人进来先要进行加锁,key值为"lock"
  9.         String value = UUID.randomUUID().toString().replace("-","");
  10.         try{
  11.             // 为key加一个过期时间
  12.             Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);
  13.             // 加锁失败
  14.             if(!flag){
  15.                 return "抢锁失败!";
  16.             }
  17.             System.out.println( value+ " 抢锁成功");
  18.             String result = template.opsForValue().get("goods:001");
  19.             int total = result == null ? 0 : Integer.parseInt(result);
  20.             if (total > 0) {
  21.                 // 如果在此处需要调用其他微服务,处理时间较长。。。
  22.                 int realTotal = total - 1;
  23.                 template.opsForValue().set("goods:001", String.valueOf(realTotal));
  24.                 System.out.println("购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001");
  25.                 return "购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001";
  26.             } else {
  27.                 System.out.println("购买商品失败,服务端口为8001");
  28.             }
  29.             return "购买商品失败,服务端口为8001";
  30.         }finally {
  31.             // 谁加的锁,谁才能删除
  32.             // 也可以使用redis事务
  33.             // https://redis.io/commands/set
  34.             // 使用Lua脚本,进行锁的删除
  35.             Jedis jedis = null;
  36.             try{
  37.                 jedis = RedisUtils.getJedis();
  38.                 String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
  39.                         "then " +
  40.                         "return redis.call('del',KEYS[1]) " +
  41.                         "else " +
  42.                         "   return 0 " +
  43.                         "end";
  44.                 Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
  45.                 if("1".equals(eval.toString())){
  46.                     System.out.println("-----del redis lock ok....");
  47.                 }else{
  48.                     System.out.println("-----del redis lock error ....");
  49.                 }
  50.             }catch (Exception e){
  51.             }finally {
  52.                 if(null != jedis){
  53.                     jedis.close();
  54.                 }
  55.             }
  56. // redis事务
  57. //            while(true){
  58. //                template.watch(REDIS_LOCK);
  59. //                if(template.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
  60. //                    template.setEnableTransactionSupport(true);
  61. //                    template.multi();
  62. //                    template.delete(REDIS_LOCK);
  63. //                    List<Object> list = template.exec();
  64. //                    if(list == null){
  65. //                        continue;
  66. //                    }
  67. //                }
  68. //                template.unwatch();
  69. //                break;
  70. //            }
  71.         }
  72.     }
  73. }
复制代码
(二) Redisson去实现

先引入maven依靠(redisson和springboot的集成包)
  1. <!-- 添加Redisson依赖 -->  
  2. <dependency>
  3.   <groupId>org.redisson</groupId>
  4.   <artifactId>redisson-spring-boot-starter</artifactId>
  5.   <version>3.15.0</version>
  6.   <exclusions>
  7.     <exclusion>
  8.       <groupId>org.redisson</groupId>
  9.       <!-- 默认是 Spring Data Redis v.2.3.x ,所以排除掉-->
  10.       <artifactId>redisson-spring-data-23</artifactId>
  11.     </exclusion>
  12.   </exclusions>
  13. </dependency>
复制代码
网上其他的有可能是引入
  1. <dependency>
  2.     <groupId>org.redisson</groupId>
  3.     <artifactId>redisson</artifactId>
  4.     <version>3.6.1</version>
  5. </dependency>
复制代码
根据以上例子用redisson去实现分布式锁,更加nice
利用Redisson的getLock方法时,你现实上是在利用RedLock(红锁)算法来获取分布式锁
  1. @RestController
  2. public class IndexController8 {
  3.     public static final String REDIS_LOCK = "lock";
  4.     @Autowired
  5.     StringRedisTemplate template;
  6.     @Autowired
  7.     Redisson redisson;
  8.     @RequestMapping("/buy8")
  9.     public String index(){
  10.         //创建锁“lock”
  11.         RLock lock = redisson.getLock(REDIS_LOCK);
  12.         //加锁
  13.         lock.lock();
  14.         
  15.         try{
  16.             String result = template.opsForValue().get("goods:001");
  17.             int total = result == null ? 0 : Integer.parseInt(result);
  18.             if (total > 0) {
  19.                 // 如果在此处需要调用其他微服务,处理时间较长。。。
  20.                 int realTotal = total - 1;
  21.                 template.opsForValue().set("goods:001", String.valueOf(realTotal));
  22.                 System.out.println("购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001");
  23.                 return "购买商品成功,库存还剩:" + realTotal + "件, 服务端口为8001";
  24.             } else {
  25.                 System.out.println("购买商品失败,服务端口为8001");
  26.             }
  27.             return "购买商品失败,服务端口为8001";
  28.         }finally {
  29.         //避免竞态条件,不要if判断检查锁的状态。需直接使用 lock.unlock();
  30. //            if(lock.isLocked() && lock.isHeldByCurrentThread()){
  31. //                lock.unlock();
  32. //            }
  33.              lock.unlock();
  34.         }
  35.     }
  36. }
复制代码
总结

现实为了保证redis高可用,redis一般会集群部署。
redis集群办理方案,利用redlock办理(redlock的特点如下):


  • 次序向5个节点哀求加锁(5个节点相互独立,没任何关系)
  • 根据超时时间来判断是否要跳过该节点
  • 如果大于等于3节点加锁乐成,并且利用时间小于锁有效期,则加锁乐成,否则获取锁失败,解锁
参考文章
【1】SpringBoot教程(十四) | SpringBoot集成Redis(全网最全)
【2】Redis实现分布式锁方法详细
【3】Redis实现分布式锁
【4】陪你一起学redis(十一)——redis分布式锁

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表