[转载]springboot2.0 集成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. public User( Integer id, String username, Integer age) {
  7. this.id = id;
  8. this.username = username;
  9. this.age = age;
  10. }
  11. public User() {
  12. }
  13. //getter/setter 省略
  14. }
  15. 复制代码

测试


   
   
  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. @Test
  8. public void test() {
  9. String key = "user:1";
  10. redisTemplate.opsForValue().set(key, new User( 1, "pjmike", 20));
  11. User user = (User) redisTemplate.opsForValue().get(key);
  12. logger.info( "uesr: "+user.toString());
  13. }
  14. }
  15. 复制代码

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. # Redis数据库索引(默认为0)
  4. spring.redis.database=0
  5. # Redis服务器地址
  6. spring.redis.host=localhost
  7. # Redis服务器连接端口
  8. spring.redis.port=6379
  9. # Redis服务器连接密码(默认为空)
  10. spring.redis.password=
  11. # #连接池最大连接数(使用负值表示没有限制) 默认8
  12. # spring.redis.lettuce.pool.max-active=8
  13. # # 连接池中的最大空闲连接 默认8
  14. # spring.redis.lettuce.pool.max-idle=8
  15. # # 连接池中的最小空闲连接 默认0
  16. # spring.redis.lettuce.pool.min-idle=0
  17. # # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
  18. # spring.redis.lettuce.pool.max-wait=-1
  19. # 连接超时时间(毫秒)
  20. 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. @Bean
  7. @ConditionalOnMissingBean(name = "redisTemplate")
  8. public RedisTemplate<Object, Object> redisTemplate(
  9. RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  10. RedisTemplate<Object, Object> template = new RedisTemplate<>();
  11. template.setConnectionFactory(redisConnectionFactory);
  12. return template;
  13. }
  14. @Bean
  15. @ConditionalOnMissingBean
  16. public StringRedisTemplate stringRedisTemplate(
  17. RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  18. StringRedisTemplate template = new StringRedisTemplate();
  19. template.setConnectionFactory(redisConnectionFactory);
  20. return template;
  21. }
  22. }

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. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer( Object.class);
  8. ObjectMapper om = new ObjectMapper();
  9. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  10. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  11. jackson2JsonRedisSerializer.setObjectMapper(om);
  12. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  13. // key采用String的序列化方式
  14. template.setKeySerializer(stringRedisSerializer);
  15. // hash的key也采用String的序列化方式
  16. template.setHashKeySerializer(stringRedisSerializer);
  17. // value序列化方式采用jackson
  18. template.setValueSerializer(jackson2JsonRedisSerializer);
  19. // hash的value序列化方式采用jackson
  20. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  21. template.afterPropertiesSet();
  22. return template;
  23. }
  24. }

注意
方法名一定要叫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. * @param key 键
  18. * @param time 时间(秒)
  19. * @return
  20. */
  21. public boolean expire( String key, long time) {
  22. try {
  23. if (time > 0) {
  24. redisTemplate.expire(key, time, TimeUnit.SECONDS);
  25. }
  26. return true;
  27. } catch ( Exception e) {
  28. e.printStackTrace();
  29. return false;
  30. }
  31. }
  32. /**
  33. * 根据key获取过期时间
  34. *
  35. * @param key 键 不能为null
  36. * @return 时间(秒) 返回0代表为永久有效
  37. */
  38. public long getExpire( String key) {
  39. return redisTemplate.getExpire(key, TimeUnit.SECONDS);
  40. }
  41. /**
  42. * 判断key是否存在
  43. *
  44. * @param key 键
  45. * @return true 存在 false不存在
  46. */
  47. public boolean hasKey( String key) {
  48. try {
  49. return redisTemplate.hasKey(key);
  50. } catch ( Exception e) {
  51. e.printStackTrace();
  52. return false;
  53. }
  54. }
  55. /**
  56. * 删除缓存
  57. *
  58. * @param key 可以传一个值 或多个
  59. */
  60. @SuppressWarnings( "unchecked")
  61. public void del( String... key) {
  62. if (key != null && key.length > 0) {
  63. if (key.length == 1) {
  64. redisTemplate.delete(key[ 0]);
  65. } else {
  66. redisTemplate.delete(CollectionUtils.arrayToList(key));
  67. }
  68. }
  69. }
  70. // ============================String(字符串)=============================
  71. /**
  72. * 普通缓存获取
  73. *
  74. * @param key 键
  75. * @return
  76. */
  77. public Object get( String key) {
  78. return key == null ? null : redisTemplate.opsForValue().get(key);
  79. }
  80. /**
  81. * 普通缓存放入
  82. *
  83. * @param key 键
  84. * @param value 值
  85. * @return true成功 false失败
  86. */
  87. public boolean set( String key, Object value) {
  88. try {
  89. redisTemplate.opsForValue().set(key, value);
  90. return true;
  91. } catch ( Exception e) {
  92. e.printStackTrace();
  93. return false;
  94. }
  95. }
  96. /**
  97. * 普通缓存放入并设置时间
  98. *
  99. * @param key 键
  100. * @param value 值
  101. * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
  102. * @return true成功 false 失败
  103. */
  104. public boolean set( String key, Object value, long time) {
  105. try {
  106. if (time > 0) {
  107. redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
  108. } else {
  109. set(key, value);
  110. }
  111. return true;
  112. } catch ( Exception e) {
  113. e.printStackTrace();
  114. return false;
  115. }
  116. }
  117. /**
  118. * 递增
  119. *
  120. * @param key 键
  121. * @param delta 要增加几(大于0)
  122. * @return
  123. */
  124. public long incr( String key, long delta) {
  125. if (delta < 0) {
  126. throw new RuntimeException( "递增因子必须大于0");
  127. }
  128. return redisTemplate.opsForValue().increment(key, delta);
  129. }
  130. /**
  131. * 递减
  132. *
  133. * @param key 键
  134. * @param delta 要减少几(小于0)
  135. * @return
  136. */
  137. public long decr( String key, long delta) {
  138. if (delta < 0) {
  139. throw new RuntimeException( "递减因子必须大于0");
  140. }
  141. return redisTemplate.opsForValue().increment(key, -delta);
  142. }
  143. // ================================Hash(哈希)=================================
  144. /**
  145. * HashGet
  146. *
  147. * @param key 键 不能为null
  148. * @param item 项 不能为null
  149. * @return
  150. */
  151. public Object hget( String key, String item) {
  152. return redisTemplate.opsForHash().get(key, item);
  153. }
  154. /**
  155. * 获取hashKey对应的所有键值
  156. *
  157. * @param key 键
  158. * @return 对应的多个键值
  159. */
  160. public Map < Object, Object> hmget( String key) {
  161. return redisTemplate.opsForHash().entries(key);
  162. }
  163. /**
  164. * HashSet
  165. *
  166. * @param key 键
  167. * @param map 对应多个键值
  168. * @return true 成功 false 失败
  169. */
  170. public boolean hmset( String key, Map < String, Object> map) {
  171. try {
  172. redisTemplate.opsForHash().putAll(key, map);
  173. return true;
  174. } catch ( Exception e) {
  175. e.printStackTrace();
  176. return false;
  177. }
  178. }
  179. /**
  180. * HashSet 并设置时间
  181. *
  182. * @param key 键
  183. * @param map 对应多个键值
  184. * @param time 时间(秒)
  185. * @return true成功 false失败
  186. */
  187. public boolean hmset( String key, Map < String, Object> map, long time) {
  188. try {
  189. redisTemplate.opsForHash().putAll(key, map);
  190. if (time > 0) {
  191. expire(key, time);
  192. }
  193. return true;
  194. } catch ( Exception e) {
  195. e.printStackTrace();
  196. return false;
  197. }
  198. }
  199. /**
  200. * 向一张hash表中放入数据,如果不存在将创建
  201. *
  202. * @param key 键
  203. * @param item 项
  204. * @param value 值
  205. * @return true 成功 false失败
  206. */
  207. public boolean hset( String key, String item, Object value) {
  208. try {
  209. redisTemplate.opsForHash().put(key, item, value);
  210. return true;
  211. } catch ( Exception e) {
  212. e.printStackTrace();
  213. return false;
  214. }
  215. }
  216. /**
  217. * 向一张hash表中放入数据,如果不存在将创建
  218. *
  219. * @param key 键
  220. * @param item 项
  221. * @param value 值
  222. * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
  223. * @return true 成功 false失败
  224. */
  225. public boolean hset( String key, String item, Object value, long time) {
  226. try {
  227. redisTemplate.opsForHash().put(key, item, value);
  228. if (time > 0) {
  229. expire(key, time);
  230. }
  231. return true;
  232. } catch ( Exception e) {
  233. e.printStackTrace();
  234. return false;
  235. }
  236. }
  237. /**
  238. * 删除hash表中的值
  239. *
  240. * @param key 键 不能为null
  241. * @param item 项 可以使多个 不能为null
  242. */
  243. public void hdel( String key, Object... item) {
  244. redisTemplate.opsForHash().delete(key, item);
  245. }
  246. /**
  247. * 判断hash表中是否有该项的值
  248. *
  249. * @param key 键 不能为null
  250. * @param item 项 不能为null
  251. * @return true 存在 false不存在
  252. */
  253. public boolean hHasKey( String key, String item) {
  254. return redisTemplate.opsForHash().hasKey(key, item);
  255. }
  256. /**
  257. * hash递增 如果不存在,就会创建一个 并把新增后的值返回
  258. *
  259. * @param key 键
  260. * @param item 项
  261. * @param by 要增加几(大于0)
  262. * @return
  263. */
  264. public double hincr( String key, String item, double by) {
  265. return redisTemplate.opsForHash().increment(key, item, by);
  266. }
  267. /**
  268. * hash递减
  269. *
  270. * @param key 键
  271. * @param item 项
  272. * @param by 要减少记(小于0)
  273. * @return
  274. */
  275. public double hdecr( String key, String item, double by) {
  276. return redisTemplate.opsForHash().increment(key, item, -by);
  277. }
  278. // ============================Set(集合)=============================
  279. /**
  280. * 根据key获取Set中的所有值
  281. *
  282. * @param key 键
  283. * @return
  284. */
  285. public Set < Object> sGet( String key) {
  286. try {
  287. return redisTemplate.opsForSet().members(key);
  288. } catch ( Exception e) {
  289. e.printStackTrace();
  290. return null;
  291. }
  292. }
  293. /**
  294. * 根据value从一个set中查询,是否存在
  295. *
  296. * @param key 键
  297. * @param value 值
  298. * @return true 存在 false不存在
  299. */
  300. public boolean sHasKey( String key, Object value) {
  301. try {
  302. return redisTemplate.opsForSet().isMember(key, value);
  303. } catch ( Exception e) {
  304. e.printStackTrace();
  305. return false;
  306. }
  307. }
  308. /**
  309. * 将数据放入set缓存
  310. *
  311. * @param key 键
  312. * @param values 值 可以是多个
  313. * @return 成功个数
  314. */
  315. public long sSet( String key, Object... values) {
  316. try {
  317. return redisTemplate.opsForSet().add(key, values);
  318. } catch ( Exception e) {
  319. e.printStackTrace();
  320. return 0;
  321. }
  322. }
  323. /**
  324. * 将set数据放入缓存
  325. *
  326. * @param key 键
  327. * @param time 时间(秒)
  328. * @param values 值 可以是多个
  329. * @return 成功个数
  330. */
  331. public long sSetAndTime( String key, long time, Object... values) {
  332. try {
  333. Long count = redisTemplate.opsForSet().add(key, values);
  334. if (time > 0)
  335. expire(key, time);
  336. return count;
  337. } catch ( Exception e) {
  338. e.printStackTrace();
  339. return 0;
  340. }
  341. }
  342. /**
  343. * 获取set缓存的长度
  344. *
  345. * @param key 键
  346. * @return
  347. */
  348. public long sGetSetSize( String key) {
  349. try {
  350. return redisTemplate.opsForSet().size(key);
  351. } catch ( Exception e) {
  352. e.printStackTrace();
  353. return 0;
  354. }
  355. }
  356. /**
  357. * 移除值为value的
  358. *
  359. * @param key 键
  360. * @param values 值 可以是多个
  361. * @return 移除的个数
  362. */
  363. public long setRemove( String key, Object... values) {
  364. try {
  365. Long count = redisTemplate.opsForSet().remove(key, values);
  366. return count;
  367. } catch ( Exception e) {
  368. e.printStackTrace();
  369. return 0;
  370. }
  371. }
  372. // ===============================List(列表)=================================
  373. /**
  374. * 获取list缓存的内容
  375. *
  376. * @param key 键
  377. * @param start 开始
  378. * @param end 结束 0 到 -1代表所有值
  379. * @return
  380. */
  381. public List < Object> lGet( String key, long start, long end) {
  382. try {
  383. return redisTemplate.opsForList().range(key, start, end);
  384. } catch ( Exception e) {
  385. e.printStackTrace();
  386. return null;
  387. }
  388. }
  389. /**
  390. * 获取list缓存的长度
  391. *
  392. * @param key 键
  393. * @return
  394. */
  395. public long lGetListSize( String key) {
  396. try {
  397. return redisTemplate.opsForList().size(key);
  398. } catch ( Exception e) {
  399. e.printStackTrace();
  400. return 0;
  401. }
  402. }
  403. /**
  404. * 通过索引 获取list中的值
  405. *
  406. * @param key 键
  407. * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
  408. * @return
  409. */
  410. public Object lGetIndex( String key, long index) {
  411. try {
  412. return redisTemplate.opsForList().index(key, index);
  413. } catch ( Exception e) {
  414. e.printStackTrace();
  415. return null;
  416. }
  417. }
  418. /**
  419. * 将list放入缓存
  420. *
  421. * @param key 键
  422. * @param value 值
  423. * @return
  424. */
  425. public boolean lSet( String key, Object value) {
  426. try {
  427. redisTemplate.opsForList().rightPush(key, value);
  428. return true;
  429. } catch ( Exception e) {
  430. e.printStackTrace();
  431. return false;
  432. }
  433. }
  434. /**
  435. * 将list放入缓存
  436. *
  437. * @param key 键
  438. * @param value 值
  439. * @param time 时间(秒)
  440. * @return
  441. */
  442. public boolean lSet( String key, Object value, long time) {
  443. try {
  444. redisTemplate.opsForList().rightPush(key, value);
  445. if (time > 0)
  446. expire(key, time);
  447. return true;
  448. } catch ( Exception e) {
  449. e.printStackTrace();
  450. return false;
  451. }
  452. }
  453. /**
  454. * 将list放入缓存
  455. *
  456. * @param key 键
  457. * @param value 值
  458. * @return
  459. */
  460. public boolean lSet( String key, List < Object> value) {
  461. try {
  462. redisTemplate.opsForList().rightPushAll(key, value);
  463. return true;
  464. } catch ( Exception e) {
  465. e.printStackTrace();
  466. return false;
  467. }
  468. }
  469. /**
  470. * 将list放入缓存
  471. *
  472. * @param key 键
  473. * @param value 值
  474. * @param time 时间(秒)
  475. * @return
  476. */
  477. public boolean lSet( String key, List < Object> value, long time) {
  478. try {
  479. redisTemplate.opsForList().rightPushAll(key, value);
  480. if (time > 0)
  481. expire(key, time);
  482. return true;
  483. } catch ( Exception e) {
  484. e.printStackTrace();
  485. return false;
  486. }
  487. }
  488. /**
  489. * 根据索引修改list中的某条数据
  490. *
  491. * @param key 键
  492. * @param index 索引
  493. * @param value 值
  494. * @return
  495. */
  496. public boolean lUpdateIndex( String key, long index, Object value) {
  497. try {
  498. redisTemplate.opsForList().set(key, index, value);
  499. return true;
  500. } catch ( Exception e) {
  501. e.printStackTrace();
  502. return false;
  503. }
  504. }
  505. /**
  506. * 移除N个值为value
  507. *
  508. * @param key 键
  509. * @param count 移除多少个
  510. * @param value 值
  511. * @return 移除的个数
  512. */
  513. public long lRemove( String key, long count, Object value) {
  514. try {
  515. Long remove = redisTemplate.opsForList().remove(key, count, value);
  516. return remove;
  517. } catch ( Exception e) {
  518. e.printStackTrace();
  519. return 0;
  520. }
  521. }
  522. }

具体测试部分就省略了。

源代码下载


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. 但跟完全 pipeline 的方式不同, 测试多次, 但没发现有一次是完整 pipeline 的
  4. 复制代码

所以如果需要使用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. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  8. ObjectMapper om = new ObjectMapper();
  9. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  10. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  11. jackson2JsonRedisSerializer.setObjectMapper(om);
  12. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  13. // key采用String的序列化方式
  14. template.setKeySerializer(stringRedisSerializer);
  15. // hash的key也采用String的序列化方式
  16. template.setHashKeySerializer(stringRedisSerializer);
  17. // value序列化方式采用jackson
  18. template.setValueSerializer(jackson2JsonRedisSerializer);
  19. // hash的value序列化方式采用jackson
  20. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  21. template.afterPropertiesSet();
  22. return template;
  23. }
  24. }
  25. 复制代码

直接引入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. @Autowired
  4. private Environment environment;
  5. /**
  6. * 配置lettuce连接池
  7. *
  8. * @return
  9. */
  10. @Bean
  11. @Primary
  12. @ConfigurationProperties(prefix = "spring.redis.cluster.lettuce.pool")
  13. public GenericObjectPoolConfig redisPool() {
  14. return new GenericObjectPoolConfig();
  15. }
  16. /**
  17. * 配置第一个数据源的
  18. *
  19. * @return
  20. */
  21. @Bean("redisClusterConfig")
  22. @Primary
  23. public RedisClusterConfiguration redisClusterConfig() {
  24. Map<String, Object> source = new HashMap<>( 8);
  25. source.put( "spring.redis.cluster.nodes", environment.getProperty( "spring.redis.cluster.nodes"));
  26. RedisClusterConfiguration redisClusterConfiguration;
  27. redisClusterConfiguration = new RedisClusterConfiguration( new MapPropertySource( "RedisClusterConfiguration", source));
  28. redisClusterConfiguration.setPassword(environment.getProperty( "spring.redis.password"));
  29. return redisClusterConfiguration;
  30. }
  31. /**
  32. * 配置第一个数据源的连接工厂
  33. * 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory
  34. *
  35. * @param redisPool
  36. * @param redisClusterConfig
  37. * @return
  38. */
  39. @Bean("lettuceConnectionFactory")
  40. @Primary
  41. public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig) {
  42. LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
  43. return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);
  44. }
  45. /**
  46. * 配置第一个数据源的RedisTemplate
  47. * 注意:这里指定使用名称=factory 的 RedisConnectionFactory
  48. * 并且标识第一个数据源是默认数据源 @Primary
  49. *
  50. * @param redisConnectionFactory
  51. * @return
  52. */
  53. @Bean("redisTemplate")
  54. @Primary
  55. public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
  56. return getRedisTemplate(redisConnectionFactory);
  57. }
  58. /**
  59. * 配置第二个数据源
  60. *
  61. * @return
  62. */
  63. @Bean("secondaryRedisClusterConfig")
  64. public RedisClusterConfiguration secondaryRedisConfig() {
  65. Map<String, Object> source = new HashMap<>( 8);
  66. source.put( "spring.redis.cluster.nodes", environment.getProperty( "spring.secondaryRedis.cluster.nodes"));
  67. RedisClusterConfiguration redisClusterConfiguration;
  68. redisClusterConfiguration = new RedisClusterConfiguration( new MapPropertySource( "RedisClusterConfiguration", source));
  69. redisClusterConfiguration.setPassword(environment.getProperty( "spring.redis.password"));
  70. return redisClusterConfiguration;
  71. }
  72. @Bean("secondaryLettuceConnectionFactory")
  73. public LettuceConnectionFactory secondaryLettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("secondaryRedisClusterConfig")RedisClusterConfiguration secondaryRedisClusterConfig) {
  74. LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
  75. return new LettuceConnectionFactory(secondaryRedisClusterConfig, clientConfiguration);
  76. }
  77. /**
  78. * 配置第一个数据源的RedisTemplate
  79. * 注意:这里指定使用名称=factory2 的 RedisConnectionFactory
  80. *
  81. * @param redisConnectionFactory
  82. * @return
  83. */
  84. @Bean("secondaryRedisTemplate")
  85. public RedisTemplate secondaryRedisTemplate(@Qualifier("secondaryLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
  86. return getRedisTemplate(redisConnectionFactory);
  87. }
  88. private RedisTemplate getRedisTemplate(RedisConnectionFactory factory) {
  89. RedisTemplate<String, Object> template = new RedisTemplate<>();
  90. template.setConnectionFactory(factory);
  91. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  92. ObjectMapper om = new ObjectMapper();
  93. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  94. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  95. jackson2JsonRedisSerializer.setObjectMapper(om);
  96. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  97. // key采用String的序列化方式
  98. template.setKeySerializer(stringRedisSerializer);
  99. // hash的key也采用String的序列化方式
  100. template.setHashKeySerializer(stringRedisSerializer);
  101. // value序列化方式采用jackson
  102. template.setValueSerializer(jackson2JsonRedisSerializer);
  103. // hash的value序列化方式采用jackson
  104. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  105. template.afterPropertiesSet();
  106. return template;
  107. }
  108. }


参考:

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值