app开发定制SpringBoot教程(十四) | SpringBoot集成Redis(全网最全)

一、Redis集成简介

Redis是我们Java开发中,app开发定制使用频次非常高的一个nosql数据库,数据以key-valueapp开发定制键值对的形式存储在中。redisapp开发定制的常用使用场景,app开发定制可以做缓存,分布式锁,app开发定制自增序列等,使用redisapp开发定制的方式和我们使用数据app开发定制库的方式差不多,app开发定制首先我们要在自己的本app开发定制机电脑或者服务器上安装一个redis的服务器,app开发定制通过我们的javaapp开发定制客户端在程序中进行集成,app开发定制然后通过客户端完成对redisapp开发定制的增删改查操作。redis的Javaapp开发定制客户端类型还是很多的,常见的有jedis, redission,lettuce等,app开发定制所以我们在集成的时候,app开发定制我们可以选择直接集成app开发定制这些原生客户端。但是在springBootapp开发定制中更常见的方式是集成spring-data-redis,这是springapp开发定制提供的一个专门用来操作redis的项目,封装了对redisapp开发定制app开发定制的常用操作,app开发定制里边主要封装了jedis和lettuceapp开发定制两个客户端。app开发定制相当于是在他们的基础app开发定制上加了一层门面。

app开发定制本篇文章我们就来重点介绍,springBoot通过集成spring-data-redis使用对于redis的常用操作。

由于不涉及到兼容问题,我们就直接在feature/MybatisPlus分支上开发。

二、集成步骤

2.1 添加依赖

添加redis所需依赖:

  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>

这里我们直接引入了spring-boot-starter-data-redis这个springBoot本身就已经提供好了的starter, 我们可以点击去看一下这个starter中包含了哪些依赖:

可以发现,里面包含了spring-data-redis和 lettuce-core两个核心包,这就是为什么说我们的spring-boot-starter-data-redis默认使用的就是lettuce这个客户端了。

如果我们想要使用jedis客户端怎么办呢?就需要排除lettuce这个依赖,再引入jedis的相关依赖就可以了。

那么为什么我们只需要通过引入不同的依赖就能让spring-data-redis可以自由切换客户端呢,这其实就涉及到了springBoot的自动化配置原理。我们可以给大家简单讲解一下。

springBoot这个框架之所以可以通过各种starter无缝融合其他技术的一大主要原因就是springBoot本身的自动化配置功能。所谓自动化配置就是springBoot本身已经预先设置好了一些常用框架的整合类。然后通过类似于ConditionOn这样的条件判断注解,去辨别你的项目中是否有相关的类(或配置)了,进而进行相关配置的初始化。

springBoot预设的自动化配置类都位于spring-boot-autoconfigure这个包中,只要我们搭建了springBoot的项目,这个包就会被引入进来。

而这个包下就有一个RedisAutoConfiguration这个类,顾名思义就是Redis的自动化配置。在这个类中,会引入LettuceConnectionConfiguration 和 JedisConnectionConfiguration 两个配置类,分别对应lettuce和jedis两个客户端。

而这个两个类上都是用了ConditionOn注解来进行判断是否加载。

jedis如下;

而由于我们的项目自动引入了lettuce-core,而没有引入jedis相关依赖,所以LettuceConnectionConfiguration这个类的判断成立会被加载,而Jedis的判断不成立,所以不会加载。进而lettuce的配置生效,所以我们在使用的使用, 默认就是lettuce的客户端。

2.2 添加配置

然后我们需要配置连接redis所需的账号密码等信息,这里大家要提前安装好redis,保证我们的本机程序可以连接到我们的redis, 如果不知道redis如何安装,可以参考文章: [Linux系统安装redis6.0.5]

常规配置如下: 在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. }

三、工具类封装

我们在前面的代码中已经通过RedisTemplate成功操作了redis服务器,比如set一个字符串,我们可以使用:

redisTemplate.opsForValue().set(key, value);

来put一个String类型的键值对。而redis中可以支持 string, list, hash,set, zset五种数据格式,这五种数据格式的常用操作,都在RedisTemplate这个类中进行了封装。 操作string类型就是用opsForValue,操作list类型是用listOps, 操作set类型是用setOps等等。我们可以通过查看RedisTemplate这个类中的源码来了解大致有哪些功能。

而这些功能都在这一个类中,使用起来其实并不是很方便,所有一般情况下,我们都是单独封装一个工具类,来把常用的一些方法进行抽象。操作的时候,直接通过工具类来操作。

  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. }

大家也可以通过阅读这个工具类,深入了解RedisTemplate的用法。使用的时候,只需要注入这个工具类就可以了。

四、讲讲序列化

redis的序列化也是我们在使用RedisTemplate的过程中需要注意的事情。上面的案例中,其实我们并没有特殊设置redis的序列化方式,那么它其实使用的是默认的序列化方式。RedisTemplate这个类的泛型是<String,Object>,也就是他是支持写入Object对象的,那么这个对象采取什么方式序列化存入内存中就是它的序列化方式。

那么什么是redis的序列化呢?就是我们把对象存入到redis中到底以什么方式存储的,可以是二进制数据,可以是xml也可以是json。比如说我们经常会将POJO 到 Redis 中,一般情况下会使用 JSON 方式序列化成字符串,存储到 Redis 中 。

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

  • GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
  • Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
  • JacksonJsonRedisSerializer: 序列化object对象为json字符串
  • JdkSerializationRedisSerializer: 序列化java对象
  • StringRedisSerializer: 简单的字符串序列化

如果我们存储的是String类型,默认使用的是StringRedisSerializer 这种序列化方式。如果我们存储的是对象,默认使用的是 JdkSerializationRedisSerializer,也就是Jdk的序列化方式(通过ObjectOutputStream和ObjectInputStream实现,缺点是我们无法直观看到存储的对象内容)。

我们可以根据redis操作的不同数据类型,设置对应的序列化方式。

通过观察RedisTemplate的源码我们就可以看出来,默认使用的是JdkSerializationRedisSerializer. 这种序列化最大的问题就是存入对象后,我们很难直观看到存储的内容,很不方便我们排查问题:

而一般我们最经常使用的对象序列化方式是: Jackson2JsonRedisSerializer

设置序列化方式的主要方法就是我们在配置类中,自己来创建RedisTemplate对象,并在创建的过程中指定对应的序列化方式。

  1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  2. import com.fasterxml.jackson.annotation.PropertyAccessor;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.data.redis.connection.RedisConnectionFactory;
  7. import org.springframework.data.redis.core.RedisTemplate;
  8. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  9. import org.springframework.data.redis.serializer.StringRedisSerializer;
  10. @Configuration
  11. public class RedisConfig {
  12. @Bean(name = "redisTemplate")
  13. public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
  14. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  15. redisTemplate.setConnectionFactory(factory);
  16. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  17. redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型
  18. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  19. ObjectMapper objectMapper = new ObjectMapper();
  20. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  21. // 方法过期,改为下面代码
  22. // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  23. objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
  24. ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  25. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  26. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  27. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
  28. redisTemplate.setHashKeySerializer(stringRedisSerializer);
  29. redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  30. redisTemplate.afterPropertiesSet();
  31. return redisTemplate;
  32. }
  33. }

这样使用的时候,就会按照我们设置的json序列化方式进行存储,我们也可以在redis中查看内容的时候方便的查看到属性值。

五、锁

参考资料:

很多场景中,需要使用分布式事务、分布式锁等技术来保证数据最终一致性。有的时候,我们需要保证某一方法同一时刻只能被一个线程执行。
在单机(单进程)环境中,JAVA提供了很多并发相关API,但在多机(多进程)环境中就无能为力了。

对于分布式锁,最好能够满足以下几点

可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
这把锁要是一把可重入锁(避免死锁)
这把锁最好是一把阻塞锁
有高可用的获取锁和释放锁功能
获取锁和释放锁的性能要好

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇文章主要介绍第二种方式。

一个完美的分布式锁,必须要满足如下四个条件: 1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

Redis分布式锁原理:

锁的实现主要基于redis的SETNX命令

SETNX key value将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

**返回值:**设置成功,返回 1 。设置失败,返回 0 。

使用SETNX完成同步锁的流程及事项如下:

  1. 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
  2. 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
  3. 释放锁,使用DEL命令将锁数据删除

这篇文章中对于Redis中的锁的介绍还是比较全面的。

Redis锁的实现方式很多,到时多多少少都有点问题,相对比较完美的方案是使用lua脚本。最完美的解决方案就是使用Redission这个框架里边的RedissionRedLock。具体实现就不给出了,大家可以按照这个思路去查找相关资料。等到我什么时候有时间和精力了再回来补充一下。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发