springboot2.x 整合redis范例(Lettuce & Jedis)

前言

在实际项目开发过程中,相信很多人都有用到过 redis 这个NoSQL,这篇文章就详细讲讲springboot如何整合 redis

Redis 简介

简单介绍下Redis:

Redis是一个开源的使用 ANSI C语言编写,支持网络,可基于内存也可持久化的日志型,Key-Value数据库,并提供了多种语言的 API ,相比 Memcached 它支持存储的类型相对更多 (字符,哈希,集合,有序集合,列表等),同时Redis是线程安全的。

Redis 连接池简介

在后面 springboot 整合 redis 的时候会用到连接池,所以这里先来介绍下 Redis中的连接池:

客户端连接 Redis 使用的是 TCP协议,直连的方式每次需要建立 TCP连接,而连接池的方式是可以预先初始化好客户端连接,所以每次只需要从 连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。另外,直连的方式无法限制 redis客户端对象的个数,在极端情况下可能会造成连接泄漏,而连接池的形式可以有效的保护和控制资源的使用。

下面以Jedis客户端为例,再来总结下 客户端直连方式和连接池方式的对比

 优点缺点
直连简单方便,适用于少量长期连接的场景1. 存在每次新建/关闭TCP连接开销 2. 资源无法控制,极端情况下出现连接泄漏 3. Jedis对象线程不安全(Lettuce对象是线程安全的)
连接池1. 无需每次连接生成Jedis对象,降低开销 2. 使用连接池的形式保护和控制资源的使用相对于直连,使用更加麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题

Jedis vs Lettuce

redis官方提供的java client有如图所示几种:

redis

 

      比较突出的是 Lettuce 和 jedis。Lettuce 和 jedis 的都是连接 Redis Server的客户端,Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。

      Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。

在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,springboot 2.x版本中默认客户端是用 lettuce实现的。

      Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。关于 Jedis 跟 Lettuce 的区别如下:

  1. Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
  2. Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

 

springboot 2.0 通过 lettuce集成Redis服务

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

</dependencies>

 

application.properties配置文件

 
  1. spring.redis.host=localhost

  2. spring.redis.port=6379

  3. spring.redis.password=root

  4. # 连接池最大连接数(使用负值表示没有限制) 默认为8

  5. spring.redis.lettuce.pool.max-active=8

  6. # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1

  7. spring.redis.lettuce.pool.max-wait=-1ms

  8. # 连接池中的最大空闲连接 默认为8

  9. spring.redis.lettuce.pool.max-idle=8

  10. # 连接池中的最小空闲连接 默认为 0

  11. spring.redis.lettuce.pool.min-idle=0

  12. 复制代码

自定义 RedisTemplate

     默认情况下的模板只能支持 RedisTemplate<String,String>,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。如下所示:

 
  1. @Configuration

  2. public class RedisConfig {

  3. @Bean

  4. public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {

  5. RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();

  6. redisTemplate.setKeySerializer(new StringRedisSerializer());

  7. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

  8. redisTemplate.setConnectionFactory(connectionFactory);

  9. return redisTemplate;

  10. }

  11. }

  12. 复制代码

定义测试实体类

 
  1. public class User implements Serializable {

  2. private static final long serialVersionUID = 4220515347228129741L;

  3. private Integer id;

  4. private String username;

  5. private Integer age;

  6.  
  7. public User(Integer id, String username, Integer age) {

  8. this.id = id;

  9. this.username = username;

  10. this.age = age;

  11. }

  12.  
  13. public User() {

  14. }

  15. //getter/setter 省略

  16. }

  17. 复制代码

测试

 
  1. @RunWith(SpringRunner.class)

  2. @SpringBootTest

  3. public class RedisTest {

  4. private Logger logger = LoggerFactory.getLogger(RedisTest.class);

  5. @Autowired

  6. private RedisTemplate<String, Serializable> redisTemplate;

  7.  
  8. @Test

  9. public void test() {

  10. String key = "user:1";

  11. redisTemplate.opsForValue().set(key, new User(1,"pjmike",20));

  12. User user = (User) redisTemplate.opsForValue().get(key);

  13. logger.info("uesr: "+user.toString());

  14. }

  15. }

  16. 复制代码

springboot 2.0 通过 jedis 集成Redis服务

导入依赖

      因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包,pom.xml配置如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

 

application.properties配置

使用jedis的连接池

 
  1. spring.redis.host=localhost

  2. spring.redis.port=6379

  3. spring.redis.password=root

  4. spring.redis.jedis.pool.max-idle=8

  5. spring.redis.jedis.pool.max-wait=-1ms

  6. spring.redis.jedis.pool.min-idle=0

  7. spring.redis.jedis.pool.max-active=8

配置 JedisConnectionFactory

     因为在 springoot 2.x版本中,默认采用的是 Lettuce实现的,所以无法初始化出 Jedis的连接对象 JedisConnectionFactory,所以我们需要手动配置并注入

 
  1. public class RedisConfig {

  2. @Bean

  3. JedisConnectionFactory jedisConnectionFactory() {

  4. JedisConnectionFactory factory = new JedisConnectionFactory();

  5. return factory;

  6. }

  7. }

但是启动项目后发现报出了如下的异常:

jedis_error

 

redis连接失败,springboot2.x通过以上方式集成Redis并不会读取配置文件中的 spring.redis.host等这样的配置,需要手动配置,如下:

@Configuration
public class RedisConfig2 {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(JedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        return redisTemplate;
    }
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        config.setPassword(RedisPassword.of(password));
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(config);
        return connectionFactory;
    }
}

      通过以上方式就可以连接上 redis了,不过这里要提醒的一点就是,在springboot 2.x版本中 JedisConnectionFactory设置连接的方法已过时,如图所示:

jedis_timeout

在 springboot 2.x版本中推荐使用 RedisStandaloneConfiguration类来设置连接的端口,地址等属性

其他例子

2.1 配置文件

lettuce的默认配置已经基本满足需求了,如果有需要可以自行配置

 
  1. ##端口号

  2. server.port=8888

  3.  
  4. # Redis数据库索引(默认为0)

  5. spring.redis.database=0

  6. # Redis服务器地址

  7. spring.redis.host=localhost

  8. # Redis服务器连接端口

  9. spring.redis.port=6379

  10. # Redis服务器连接密码(默认为空)

  11. spring.redis.password=

  12. ##连接池最大连接数(使用负值表示没有限制) 默认8

  13. #spring.redis.lettuce.pool.max-active=8

  14. ## 连接池中的最大空闲连接 默认8

  15. #spring.redis.lettuce.pool.max-idle=8

  16. ## 连接池中的最小空闲连接 默认0

  17. #spring.redis.lettuce.pool.min-idle=0

  18. ## 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1

  19. #spring.redis.lettuce.pool.max-wait=-1

  20. # 连接超时时间(毫秒)

  21. spring.redis.timeout=200

2.2 RredisTemplate 配置

2.2.1 RredisTemplate自动配置

在引入redis的依赖后,RredisTemplate会自动配置,可以直接注入RedisTemplate使用。

 
  1. @Configuration

  2. @ConditionalOnClass(RedisOperations.class)

  3. @EnableConfigurationProperties(RedisProperties.class)

  4. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })

  5. public class RedisAutoConfiguration {

  6.  
  7. @Bean

  8. @ConditionalOnMissingBean(name = "redisTemplate")

  9. public RedisTemplate<Object, Object> redisTemplate(

  10. RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {

  11. RedisTemplate<Object, Object> template = new RedisTemplate<>();

  12. template.setConnectionFactory(redisConnectionFactory);

  13. return template;

  14. }

  15.  
  16. @Bean

  17. @ConditionalOnMissingBean

  18. public StringRedisTemplate stringRedisTemplate(

  19. RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {

  20. StringRedisTemplate template = new StringRedisTemplate();

  21. template.setConnectionFactory(redisConnectionFactory);

  22. return template;

  23. }

  24.  
  25. }

Spring Boot 自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。但是,这个RedisTemplate的泛型是<Object,Object>。这样在写代码就很不方便,要写好多类型转换的代码。

因为有@ConditionalOnMissingBean(name = "redisTemplate")注解,所以如果Spring容器中有一个name 为redisTemplate 的 RedisTemplate 对象那么这个自动配置的RedisTemplate就不会实例化。

2.2.2 配置一个RredisTemplate

我们需要一个泛型为<String,Object>形式的RedisTemplate,并且设置这个RedisTemplate在数据存在Redis时key及value的序列化方式(默认使用的JdkSerializationRedisSerializer 这样的会导致我们通过redis desktop manager显示的我们key跟value的时候显示不是正常字符)。

 
  1. @Configuration

  2. public class RedisConfig {

  3. @Bean

  4. public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){

  5. RedisTemplate<String,Object> template = new RedisTemplate <>();

  6. template.setConnectionFactory(factory);

  7.  
  8. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

  9. ObjectMapper om = new ObjectMapper();

  10. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

  11. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

  12. jackson2JsonRedisSerializer.setObjectMapper(om);

  13.  
  14. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

  15. // key采用String的序列化方式

  16. template.setKeySerializer(stringRedisSerializer);

  17. // hash的key也采用String的序列化方式

  18. template.setHashKeySerializer(stringRedisSerializer);

  19. // value序列化方式采用jackson

  20. template.setValueSerializer(jackson2JsonRedisSerializer);

  21. // hash的value序列化方式采用jackson

  22. template.setHashValueSerializer(jackson2JsonRedisSerializer);

  23. template.afterPropertiesSet();

  24.  
  25. return template;

  26. }

  27. }

注意
方法名一定要叫redisTemplate 因为@Bean注解是根据方法名配置这个bean的name的,覆盖默认配置

2.3 Redis 操作的工具类

下面这个工具类包含Redis的一些基本操作,大家可以参考

 
  1. /**

  2. * redis 工具类

  3. *

  4. * @author simon

  5. * @date 2018-11-28 10:35

  6. **/

  7. @Component

  8. public class RedisUtils {

  9. /**

  10. * 注入redisTemplate bean

  11. */

  12. @Autowired

  13. private RedisTemplate <String,Object> redisTemplate;

  14.  
  15. /**

  16. * 指定缓存失效时间

  17. *

  18. * @param key 键

  19. * @param time 时间(秒)

  20. * @return

  21. */

  22. public boolean expire(String key, long time) {

  23. try {

  24. if (time > 0) {

  25. redisTemplate.expire(key, time, TimeUnit.SECONDS);

  26. }

  27. return true;

  28. } catch (Exception e) {

  29. e.printStackTrace();

  30. return false;

  31. }

  32. }

  33.  
  34. /**

  35. * 根据key获取过期时间

  36. *

  37. * @param key 键 不能为null

  38. * @return 时间(秒) 返回0代表为永久有效

  39. */

  40. public long getExpire(String key) {

  41. return redisTemplate.getExpire(key, TimeUnit.SECONDS);

  42. }

  43.  
  44. /**

  45. * 判断key是否存在

  46. *

  47. * @param key 键

  48. * @return true 存在 false不存在

  49. */

  50. public boolean hasKey(String key) {

  51. try {

  52. return redisTemplate.hasKey(key);

  53. } catch (Exception e) {

  54. e.printStackTrace();

  55. return false;

  56. }

  57. }

  58.  
  59. /**

  60. * 删除缓存

  61. *

  62. * @param key 可以传一个值 或多个

  63. */

  64. @SuppressWarnings("unchecked")

  65. public void del(String... key) {

  66. if (key != null && key.length > 0) {

  67. if (key.length == 1) {

  68. redisTemplate.delete(key[0]);

  69. } else {

  70. redisTemplate.delete(CollectionUtils.arrayToList(key));

  71. }

  72. }

  73. }

  74. // ============================String(字符串)=============================

  75.  
  76. /**

  77. * 普通缓存获取

  78. *

  79. * @param key 键

  80. * @return 值

  81. */

  82. public Object get(String key) {

  83. return key == null ? null : redisTemplate.opsForValue().get(key);

  84. }

  85.  
  86. /**

  87. * 普通缓存放入

  88. *

  89. * @param key 键

  90. * @param value 值

  91. * @return true成功 false失败

  92. */

  93. public boolean set(String key, Object value) {

  94. try {

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

  96. return true;

  97. } catch (Exception e) {

  98. e.printStackTrace();

  99. return false;

  100. }

  101. }

  102.  
  103. /**

  104. * 普通缓存放入并设置时间

  105. *

  106. * @param key 键

  107. * @param value 值

  108. * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期

  109. * @return true成功 false 失败

  110. */

  111. public boolean set(String key, Object value, long time) {

  112. try {

  113. if (time > 0) {

  114. redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

  115. } else {

  116. set(key, value);

  117. }

  118. return true;

  119. } catch (Exception e) {

  120. e.printStackTrace();

  121. return false;

  122. }

  123. }

  124.  
  125. /**

  126. * 递增

  127. *

  128. * @param key 键

  129. * @param delta 要增加几(大于0)

  130. * @return

  131. */

  132. public long incr(String key, long delta) {

  133. if (delta < 0) {

  134. throw new RuntimeException("递增因子必须大于0");

  135. }

  136. return redisTemplate.opsForValue().increment(key, delta);

  137. }

  138.  
  139. /**

  140. * 递减

  141. *

  142. * @param key 键

  143. * @param delta 要减少几(小于0)

  144. * @return

  145. */

  146. public long decr(String key, long delta) {

  147. if (delta < 0) {

  148. throw new RuntimeException("递减因子必须大于0");

  149. }

  150. return redisTemplate.opsForValue().increment(key, -delta);

  151. }

  152. // ================================Hash(哈希)=================================

  153.  
  154. /**

  155. * HashGet

  156. *

  157. * @param key 键 不能为null

  158. * @param item 项 不能为null

  159. * @return 值

  160. */

  161. public Object hget(String key, String item) {

  162. return redisTemplate.opsForHash().get(key, item);

  163. }

  164.  
  165. /**

  166. * 获取hashKey对应的所有键值

  167. *

  168. * @param key 键

  169. * @return 对应的多个键值

  170. */

  171. public Map <Object, Object> hmget(String key) {

  172. return redisTemplate.opsForHash().entries(key);

  173. }

  174.  
  175. /**

  176. * HashSet

  177. *

  178. * @param key 键

  179. * @param map 对应多个键值

  180. * @return true 成功 false 失败

  181. */

  182. public boolean hmset(String key, Map <String, Object> map) {

  183. try {

  184. redisTemplate.opsForHash().putAll(key, map);

  185. return true;

  186. } catch (Exception e) {

  187. e.printStackTrace();

  188. return false;

  189. }

  190. }

  191.  
  192. /**

  193. * HashSet 并设置时间

  194. *

  195. * @param key 键

  196. * @param map 对应多个键值

  197. * @param time 时间(秒)

  198. * @return true成功 false失败

  199. */

  200. public boolean hmset(String key, Map <String, Object> map, long time) {

  201. try {

  202. redisTemplate.opsForHash().putAll(key, map);

  203. if (time > 0) {

  204. expire(key, time);

  205. }

  206. return true;

  207. } catch (Exception e) {

  208. e.printStackTrace();

  209. return false;

  210. }

  211. }

  212.  
  213. /**

  214. * 向一张hash表中放入数据,如果不存在将创建

  215. *

  216. * @param key 键

  217. * @param item 项

  218. * @param value 值

  219. * @return true 成功 false失败

  220. */

  221. public boolean hset(String key, String item, Object value) {

  222. try {

  223. redisTemplate.opsForHash().put(key, item, value);

  224. return true;

  225. } catch (Exception e) {

  226. e.printStackTrace();

  227. return false;

  228. }

  229. }

  230.  
  231. /**

  232. * 向一张hash表中放入数据,如果不存在将创建

  233. *

  234. * @param key 键

  235. * @param item 项

  236. * @param value 值

  237. * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间

  238. * @return true 成功 false失败

  239. */

  240. public boolean hset(String key, String item, Object value, long time) {

  241. try {

  242. redisTemplate.opsForHash().put(key, item, value);

  243. if (time > 0) {

  244. expire(key, time);

  245. }

  246. return true;

  247. } catch (Exception e) {

  248. e.printStackTrace();

  249. return false;

  250. }

  251. }

  252.  
  253. /**

  254. * 删除hash表中的值

  255. *

  256. * @param key 键 不能为null

  257. * @param item 项 可以使多个 不能为null

  258. */

  259. public void hdel(String key, Object... item) {

  260. redisTemplate.opsForHash().delete(key, item);

  261. }

  262.  
  263. /**

  264. * 判断hash表中是否有该项的值

  265. *

  266. * @param key 键 不能为null

  267. * @param item 项 不能为null

  268. * @return true 存在 false不存在

  269. */

  270. public boolean hHasKey(String key, String item) {

  271. return redisTemplate.opsForHash().hasKey(key, item);

  272. }

  273.  
  274. /**

  275. * hash递增 如果不存在,就会创建一个 并把新增后的值返回

  276. *

  277. * @param key 键

  278. * @param item 项

  279. * @param by 要增加几(大于0)

  280. * @return

  281. */

  282. public double hincr(String key, String item, double by) {

  283. return redisTemplate.opsForHash().increment(key, item, by);

  284. }

  285.  
  286. /**

  287. * hash递减

  288. *

  289. * @param key 键

  290. * @param item 项

  291. * @param by 要减少记(小于0)

  292. * @return

  293. */

  294. public double hdecr(String key, String item, double by) {

  295. return redisTemplate.opsForHash().increment(key, item, -by);

  296. }

  297. // ============================Set(集合)=============================

  298.  
  299. /**

  300. * 根据key获取Set中的所有值

  301. *

  302. * @param key 键

  303. * @return

  304. */

  305. public Set <Object> sGet(String key) {

  306. try {

  307. return redisTemplate.opsForSet().members(key);

  308. } catch (Exception e) {

  309. e.printStackTrace();

  310. return null;

  311. }

  312. }

  313.  
  314. /**

  315. * 根据value从一个set中查询,是否存在

  316. *

  317. * @param key 键

  318. * @param value 值

  319. * @return true 存在 false不存在

  320. */

  321. public boolean sHasKey(String key, Object value) {

  322. try {

  323. return redisTemplate.opsForSet().isMember(key, value);

  324. } catch (Exception e) {

  325. e.printStackTrace();

  326. return false;

  327. }

  328. }

  329.  
  330. /**

  331. * 将数据放入set缓存

  332. *

  333. * @param key 键

  334. * @param values 值 可以是多个

  335. * @return 成功个数

  336. */

  337. public long sSet(String key, Object... values) {

  338. try {

  339. return redisTemplate.opsForSet().add(key, values);

  340. } catch (Exception e) {

  341. e.printStackTrace();

  342. return 0;

  343. }

  344. }

  345.  
  346. /**

  347. * 将set数据放入缓存

  348. *

  349. * @param key 键

  350. * @param time 时间(秒)

  351. * @param values 值 可以是多个

  352. * @return 成功个数

  353. */

  354. public long sSetAndTime(String key, long time, Object... values) {

  355. try {

  356. Long count = redisTemplate.opsForSet().add(key, values);

  357. if (time > 0)

  358. expire(key, time);

  359. return count;

  360. } catch (Exception e) {

  361. e.printStackTrace();

  362. return 0;

  363. }

  364. }

  365.  
  366. /**

  367. * 获取set缓存的长度

  368. *

  369. * @param key 键

  370. * @return

  371. */

  372. public long sGetSetSize(String key) {

  373. try {

  374. return redisTemplate.opsForSet().size(key);

  375. } catch (Exception e) {

  376. e.printStackTrace();

  377. return 0;

  378. }

  379. }

  380.  
  381. /**

  382. * 移除值为value的

  383. *

  384. * @param key 键

  385. * @param values 值 可以是多个

  386. * @return 移除的个数

  387. */

  388. public long setRemove(String key, Object... values) {

  389. try {

  390. Long count = redisTemplate.opsForSet().remove(key, values);

  391. return count;

  392. } catch (Exception e) {

  393. e.printStackTrace();

  394. return 0;

  395. }

  396. }

  397. // ===============================List(列表)=================================

  398.  
  399. /**

  400. * 获取list缓存的内容

  401. *

  402. * @param key 键

  403. * @param start 开始

  404. * @param end 结束 0 到 -1代表所有值

  405. * @return

  406. */

  407. public List <Object> lGet(String key, long start, long end) {

  408. try {

  409. return redisTemplate.opsForList().range(key, start, end);

  410. } catch (Exception e) {

  411. e.printStackTrace();

  412. return null;

  413. }

  414. }

  415.  
  416. /**

  417. * 获取list缓存的长度

  418. *

  419. * @param key 键

  420. * @return

  421. */

  422. public long lGetListSize(String key) {

  423. try {

  424. return redisTemplate.opsForList().size(key);

  425. } catch (Exception e) {

  426. e.printStackTrace();

  427. return 0;

  428. }

  429. }

  430.  
  431. /**

  432. * 通过索引 获取list中的值

  433. *

  434. * @param key 键

  435. * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推

  436. * @return

  437. */

  438. public Object lGetIndex(String key, long index) {

  439. try {

  440. return redisTemplate.opsForList().index(key, index);

  441. } catch (Exception e) {

  442. e.printStackTrace();

  443. return null;

  444. }

  445. }

  446.  
  447. /**

  448. * 将list放入缓存

  449. *

  450. * @param key 键

  451. * @param value 值

  452. * @return

  453. */

  454. public boolean lSet(String key, Object value) {

  455. try {

  456. redisTemplate.opsForList().rightPush(key, value);

  457. return true;

  458. } catch (Exception e) {

  459. e.printStackTrace();

  460. return false;

  461. }

  462. }

  463.  
  464. /**

  465. * 将list放入缓存

  466. *

  467. * @param key 键

  468. * @param value 值

  469. * @param time 时间(秒)

  470. * @return

  471. */

  472. public boolean lSet(String key, Object value, long time) {

  473. try {

  474. redisTemplate.opsForList().rightPush(key, value);

  475. if (time > 0)

  476. expire(key, time);

  477. return true;

  478. } catch (Exception e) {

  479. e.printStackTrace();

  480. return false;

  481. }

  482. }

  483.  
  484. /**

  485. * 将list放入缓存

  486. *

  487. * @param key 键

  488. * @param value 值

  489. * @return

  490. */

  491. public boolean lSet(String key, List <Object> value) {

  492. try {

  493. redisTemplate.opsForList().rightPushAll(key, value);

  494. return true;

  495. } catch (Exception e) {

  496. e.printStackTrace();

  497. return false;

  498. }

  499. }

  500.  
  501. /**

  502. * 将list放入缓存

  503. *

  504. * @param key 键

  505. * @param value 值

  506. * @param time 时间(秒)

  507. * @return

  508. */

  509. public boolean lSet(String key, List <Object> value, long time) {

  510. try {

  511. redisTemplate.opsForList().rightPushAll(key, value);

  512. if (time > 0)

  513. expire(key, time);

  514. return true;

  515. } catch (Exception e) {

  516. e.printStackTrace();

  517. return false;

  518. }

  519. }

  520.  
  521. /**

  522. * 根据索引修改list中的某条数据

  523. *

  524. * @param key 键

  525. * @param index 索引

  526. * @param value 值

  527. * @return

  528. */

  529. public boolean lUpdateIndex(String key, long index, Object value) {

  530. try {

  531. redisTemplate.opsForList().set(key, index, value);

  532. return true;

  533. } catch (Exception e) {

  534. e.printStackTrace();

  535. return false;

  536. }

  537. }

  538.  
  539. /**

  540. * 移除N个值为value

  541. *

  542. * @param key 键

  543. * @param count 移除多少个

  544. * @param value 值

  545. * @return 移除的个数

  546. */

  547. public long lRemove(String key, long count, Object value) {

  548. try {

  549. Long remove = redisTemplate.opsForList().remove(key, count, value);

  550. return remove;

  551. } catch (Exception e) {

  552. e.printStackTrace();

  553. return 0;

  554. }

  555. }

  556. }

具体测试部分就省略了。

源代码下载


SpringBoot2.X整合Redis(单机+集群+多数据源)-Lettuce版

 

Redis 三大客户端

简介

Jedis:是Redis 老牌的Java实现客户端,提供了比较全面的Redis命令的支持,

Redisson:实现了分布式和可扩展的Java数据结构。

Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

优点:

Jedis:比较全面的提供了Redis的操作特性

Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如分布式锁,分布式集合,可通过Redis支持延迟队列

Lettuce:主要在一些分布式缓存框架上使用比较多

可伸缩:

Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis

Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作

Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

pipeline 的支持

jedis 通过一定的改造后可以支持pipeline, 具体可以看 Redis 批量操作之 pipeline

但是 Lettuce 的pipeline行为很奇怪. 在 Spring RedisTemplate 中的 executePipelined 方法中的情况:

 
  1. 有时完全是一条一条命令地发送

  2.  
  3. 有时全合并几条命令发送

  4.  
  5. 但跟完全 pipeline 的方式不同, 测试多次, 但没发现有一次是完整 pipeline 的

  6. 复制代码

所以如果需要使用pipeline的话, 建议还是使用Jedis

Lettuce 接入

单机版

配置文件

 
  1. host: 192.168.131.118

  2. port: 4884

  3. password: dsgs548

  4. database: 0

  5. # lettuce简单配置

  6. lettuce:

  7. pool:

  8. # 最大活跃链接数 默认8

  9. max-active: 5

  10. # 最大空闲连接数 默认8

  11. max-idle: 10

  12. # 最小空闲连接数 默认0

  13. min-idle: 0

  14. 复制代码

redis配置类

 
  1. @Configuration

  2. public class RedisConfig {

  3. @Bean

  4. public RedisTemplate redisTemplate(RedisConnectionFactory factory) {

  5. RedisTemplate<String, Object> template = new RedisTemplate<>();

  6. template.setConnectionFactory(factory);

  7.  
  8. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

  9. ObjectMapper om = new ObjectMapper();

  10. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

  11. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

  12. jackson2JsonRedisSerializer.setObjectMapper(om);

  13.  
  14. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

  15. // key采用String的序列化方式

  16. template.setKeySerializer(stringRedisSerializer);

  17. // hash的key也采用String的序列化方式

  18. template.setHashKeySerializer(stringRedisSerializer);

  19. // value序列化方式采用jackson

  20. template.setValueSerializer(jackson2JsonRedisSerializer);

  21. // hash的value序列化方式采用jackson

  22. template.setHashValueSerializer(jackson2JsonRedisSerializer);

  23. template.afterPropertiesSet();

  24.  
  25. return template;

  26. }

  27. }

  28. 复制代码

直接引入RedisTemplate 即可, 单机版比较简单

集群版+多数据源

配置文件

 
  1. spring:

  2. redis:

  3. cluster:

  4. nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885

  5. # nodes:

  6. # - 192.168.131.118:4883

  7. # - 1192.168.131.118:4884

  8. # - 192.168.131.118:4885

  9. password: adfafsas

  10. lettuce:

  11. pool:

  12. # 最大活跃链接数 默认8

  13. max-active: 5

  14. # 最大空闲连接数 默认8

  15. max-idle: 10

  16. # 最小空闲连接数 默认0

  17. min-idle: 0

  18. secondaryRedis:

  19. cluster:

  20. nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885

  21. # nodes:

  22. # - 192.168.131.118:4883

  23. # - 192.168.131.118:4884

  24. # - 192.168.131.118:4885

  25. password: advfafasfsa

  26. 复制代码

redis配置类

 
  1. @Configuration

  2. public class RedisConfig {

  3.  
  4. @Autowired

  5. private Environment environment;

  6. /**

  7. * 配置lettuce连接池

  8. *

  9. * @return

  10. */

  11. @Bean

  12. @Primary

  13. @ConfigurationProperties(prefix = "spring.redis.cluster.lettuce.pool")

  14. public GenericObjectPoolConfig redisPool() {

  15. return new GenericObjectPoolConfig();

  16. }

  17.  
  18. /**

  19. * 配置第一个数据源的

  20. *

  21. * @return

  22. */

  23. @Bean("redisClusterConfig")

  24. @Primary

  25. public RedisClusterConfiguration redisClusterConfig() {

  26.  
  27. Map<String, Object> source = new HashMap<>(8);

  28. source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));

  29. RedisClusterConfiguration redisClusterConfiguration;

  30. redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));

  31. redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));

  32. return redisClusterConfiguration;

  33.  
  34. }

  35.  
  36.  
  37. /**

  38. * 配置第一个数据源的连接工厂

  39. * 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory

  40. *

  41. * @param redisPool

  42. * @param redisClusterConfig

  43. * @return

  44. */

  45. @Bean("lettuceConnectionFactory")

  46. @Primary

  47. public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig) {

  48. LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();

  49. return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);

  50. }

  51.  
  52.  
  53. /**

  54. * 配置第一个数据源的RedisTemplate

  55. * 注意:这里指定使用名称=factory 的 RedisConnectionFactory

  56. * 并且标识第一个数据源是默认数据源 @Primary

  57. *

  58. * @param redisConnectionFactory

  59. * @return

  60. */

  61. @Bean("redisTemplate")

  62. @Primary

  63. public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {

  64. return getRedisTemplate(redisConnectionFactory);

  65.  
  66. }

  67.  
  68.  
  69.  
  70.  
  71. /**

  72. * 配置第二个数据源

  73. *

  74. * @return

  75. */

  76. @Bean("secondaryRedisClusterConfig")

  77. public RedisClusterConfiguration secondaryRedisConfig() {

  78.  
  79. Map<String, Object> source = new HashMap<>(8);

  80. source.put("spring.redis.cluster.nodes", environment.getProperty("spring.secondaryRedis.cluster.nodes"));

  81. RedisClusterConfiguration redisClusterConfiguration;

  82. redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));

  83. redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));

  84.  
  85. return redisClusterConfiguration;

  86. }

  87.  
  88. @Bean("secondaryLettuceConnectionFactory")

  89. public LettuceConnectionFactory secondaryLettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("secondaryRedisClusterConfig")RedisClusterConfiguration secondaryRedisClusterConfig) {

  90. LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();

  91. return new LettuceConnectionFactory(secondaryRedisClusterConfig, clientConfiguration);

  92. }

  93.  
  94. /**

  95. * 配置第一个数据源的RedisTemplate

  96. * 注意:这里指定使用名称=factory2 的 RedisConnectionFactory

  97. *

  98. * @param redisConnectionFactory

  99. * @return

  100. */

  101. @Bean("secondaryRedisTemplate")

  102. public RedisTemplate secondaryRedisTemplate(@Qualifier("secondaryLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {

  103. return getRedisTemplate(redisConnectionFactory);

  104. }

  105.  
  106.  
  107.  
  108. private RedisTemplate getRedisTemplate(RedisConnectionFactory factory) {

  109. RedisTemplate<String, Object> template = new RedisTemplate<>();

  110. template.setConnectionFactory(factory);

  111.  
  112. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

  113. ObjectMapper om = new ObjectMapper();

  114. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

  115. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

  116. jackson2JsonRedisSerializer.setObjectMapper(om);

  117.  
  118. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

  119. // key采用String的序列化方式

  120. template.setKeySerializer(stringRedisSerializer);

  121. // hash的key也采用String的序列化方式

  122. template.setHashKeySerializer(stringRedisSerializer);

  123. // value序列化方式采用jackson

  124. template.setValueSerializer(jackson2JsonRedisSerializer);

  125. // hash的value序列化方式采用jackson

  126. template.setHashValueSerializer(jackson2JsonRedisSerializer);

  127. template.afterPropertiesSet();

  128.  
  129. return template;

  130. }

  131.  
  132. }



参考:

https://www.jianshu.com/p/071bae3834b0
https://www.tapme.top/blog/detail/2019-02-22-14-59/

https://www.nonelonely.com/article/1556289630491

https://juejin.im/post/5d65eb88e51d4561db5e3a79

https://www.jianshu.com/p/0d4aea41a70c

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值