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有如图所示几种:

springboot2.0 集成redis服务详解,以及 (Lettuce & Jedis)_第1张图片

 

      比较突出的是 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服务

导入依赖


       
            org.springframework.boot
            spring-boot-starter
       

       
            org.springframework.boot
            spring-boot-starter-web
       

       
            org.springframework.boot
            spring-boot-starter-test
            test
       

       
            org.springframework.boot
            spring-boot-starter-data-redis
       

       
            org.apache.commons
            commons-pool2
        

 

application.properties配置文件

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=root
# 连接池最大连接数(使用负值表示没有限制) 默认为8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认为8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认为 0
spring.redis.lettuce.pool.min-idle=0
复制代码

自定义 RedisTemplate

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

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}
复制代码

定义测试实体类

public class User implements Serializable {
    private static final long serialVersionUID = 4220515347228129741L;
    private Integer id;
    private String username;
    private Integer age;

    public User(Integer id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }

    public User() {
    }
    //getter/setter 省略
}
复制代码

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
    private Logger logger = LoggerFactory.getLogger(RedisTest.class);
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() {
        String key = "user:1";
        redisTemplate.opsForValue().set(key, new User(1,"pjmike",20));
        User user = (User) redisTemplate.opsForValue().get(key);
        logger.info("uesr: "+user.toString());
    }
}
复制代码

springboot 2.0 通过 jedis 集成Redis服务

导入依赖

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


    org.springframework.boot
    spring-boot-starter


    org.springframework.boot
    spring-boot-starter-web


    org.springframework.boot
    spring-boot-starter-data-redis
   
       
            io.lettuce
            lettuce-core
       

   



    org.springframework.boot
    spring-boot-starter-test
    test


    redis.clients
    jedis

 

application.properties配置

使用jedis的连接池

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=root
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-active=8

配置 JedisConnectionFactory

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

public class RedisConfig {
    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        return factory;
    }
}

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

springboot2.0 集成redis服务详解,以及 (Lettuce & Jedis)_第2张图片

 

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 redisTemplate(JedisConnectionFactory connectionFactory) {
        RedisTemplate 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设置连接的方法已过时,如图所示:

springboot2.0 集成redis服务详解,以及 (Lettuce & Jedis)_第3张图片

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

其他例子

2.1 配置文件

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

##端口号
server.port=8888

# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
##连接池最大连接数(使用负值表示没有限制) 默认8
#spring.redis.lettuce.pool.max-active=8
## 连接池中的最大空闲连接 默认8
#spring.redis.lettuce.pool.max-idle=8
## 连接池中的最小空闲连接 默认0
#spring.redis.lettuce.pool.min-idle=0
## 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
#spring.redis.lettuce.pool.max-wait=-1
# 连接超时时间(毫秒)
spring.redis.timeout=200

2.2 RredisTemplate 配置

2.2.1 RredisTemplate自动配置

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

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

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

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

2.2.2 配置一个RredisTemplate

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

@Configuration
public class RedisConfig {
  @Bean
  public RedisTemplate redisTemplate(RedisConnectionFactory factory){
    RedisTemplate template = new RedisTemplate <>();
    template.setConnectionFactory(factory);

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    // key采用String的序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // hash的key也采用String的序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // value序列化方式采用jackson
    template.setValueSerializer(jackson2JsonRedisSerializer);
    // hash的value序列化方式采用jackson
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();

    return template;
  }
}

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

2.3 Redis 操作的工具类

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

/**
 * redis 工具类
 *
 * @author simon
 * @date 2018-11-28 10:35
 **/
 @Component
 public class RedisUtils {
   /**
    * 注入redisTemplate bean
    */
   @Autowired
   private RedisTemplate  redisTemplate;

   /**
    * 指定缓存失效时间
    *
    * @param key  键
    * @param time 时间(秒)
    * @return
    */
   public boolean expire(String key, long time) {
     try {
       if (time > 0) {
         redisTemplate.expire(key, time, TimeUnit.SECONDS);
       }
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 根据key获取过期时间
    *
    * @param key 键 不能为null
    * @return 时间(秒) 返回0代表为永久有效
    */
   public long getExpire(String key) {
     return redisTemplate.getExpire(key, TimeUnit.SECONDS);
   }

   /**
    * 判断key是否存在
    *
    * @param key 键
    * @return true 存在 false不存在
    */
   public boolean hasKey(String key) {
     try {
       return redisTemplate.hasKey(key);
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 删除缓存
    *
    * @param key 可以传一个值 或多个
    */
   @SuppressWarnings("unchecked")
   public void del(String... key) {
     if (key != null && key.length > 0) {
       if (key.length == 1) {
         redisTemplate.delete(key[0]);
       } else {
         redisTemplate.delete(CollectionUtils.arrayToList(key));
       }
     }
   }
   // ============================String(字符串)=============================

   /**
    * 普通缓存获取
    *
    * @param key 键
    * @return 值
    */
   public Object get(String key) {
     return key == null ? null : redisTemplate.opsForValue().get(key);
   }

   /**
    * 普通缓存放入
    *
    * @param key   键
    * @param value 值
    * @return true成功 false失败
    */
   public boolean set(String key, Object value) {
     try {
       redisTemplate.opsForValue().set(key, value);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 普通缓存放入并设置时间
    *
    * @param key   键
    * @param value 值
    * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
    * @return true成功 false 失败
    */
   public boolean set(String key, Object value, long time) {
     try {
       if (time > 0) {
         redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
       } else {
         set(key, value);
       }
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 递增
    *
    * @param key   键
    * @param delta 要增加几(大于0)
    * @return
    */
   public long incr(String key, long delta) {
     if (delta < 0) {
       throw new RuntimeException("递增因子必须大于0");
     }
     return redisTemplate.opsForValue().increment(key, delta);
   }

   /**
    * 递减
    *
    * @param key   键
    * @param delta 要减少几(小于0)
    * @return
    */
   public long decr(String key, long delta) {
     if (delta < 0) {
       throw new RuntimeException("递减因子必须大于0");
     }
     return redisTemplate.opsForValue().increment(key, -delta);
   }
   // ================================Hash(哈希)=================================

   /**
    * HashGet
    *
    * @param key  键 不能为null
    * @param item 项 不能为null
    * @return 值
    */
   public Object hget(String key, String item) {
     return redisTemplate.opsForHash().get(key, item);
   }

   /**
    * 获取hashKey对应的所有键值
    *
    * @param key 键
    * @return 对应的多个键值
    */
   public Map  hmget(String key) {
     return redisTemplate.opsForHash().entries(key);
   }

   /**
    * HashSet
    *
    * @param key 键
    * @param map 对应多个键值
    * @return true 成功 false 失败
    */
   public boolean hmset(String key, Map  map) {
     try {
       redisTemplate.opsForHash().putAll(key, map);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * HashSet 并设置时间
    *
    * @param key  键
    * @param map  对应多个键值
    * @param time 时间(秒)
    * @return true成功 false失败
    */
   public boolean hmset(String key, Map  map, long time) {
     try {
       redisTemplate.opsForHash().putAll(key, map);
       if (time > 0) {
         expire(key, time);
       }
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 向一张hash表中放入数据,如果不存在将创建
    *
    * @param key   键
    * @param item  项
    * @param value 值
    * @return true 成功 false失败
    */
   public boolean hset(String key, String item, Object value) {
     try {
       redisTemplate.opsForHash().put(key, item, value);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 向一张hash表中放入数据,如果不存在将创建
    *
    * @param key   键
    * @param item  项
    * @param value 值
    * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
    * @return true 成功 false失败
    */
   public boolean hset(String key, String item, Object value, long time) {
     try {
       redisTemplate.opsForHash().put(key, item, value);
       if (time > 0) {
         expire(key, time);
       }
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 删除hash表中的值
    *
    * @param key  键 不能为null
    * @param item 项 可以使多个 不能为null
    */
   public void hdel(String key, Object... item) {
     redisTemplate.opsForHash().delete(key, item);
   }

   /**
    * 判断hash表中是否有该项的值
    *
    * @param key  键 不能为null
    * @param item 项 不能为null
    * @return true 存在 false不存在
    */
   public boolean hHasKey(String key, String item) {
     return redisTemplate.opsForHash().hasKey(key, item);
   }

   /**
    * hash递增 如果不存在,就会创建一个 并把新增后的值返回
    *
    * @param key  键
    * @param item 项
    * @param by   要增加几(大于0)
    * @return
    */
   public double hincr(String key, String item, double by) {
     return redisTemplate.opsForHash().increment(key, item, by);
   }

   /**
    * hash递减
    *
    * @param key  键
    * @param item 项
    * @param by   要减少记(小于0)
    * @return
    */
   public double hdecr(String key, String item, double by) {
     return redisTemplate.opsForHash().increment(key, item, -by);
   }
   // ============================Set(集合)=============================

   /**
    * 根据key获取Set中的所有值
    *
    * @param key 键
    * @return
    */
   public Set  sGet(String key) {
     try {
       return redisTemplate.opsForSet().members(key);
     } catch (Exception e) {
       e.printStackTrace();
       return null;
     }
   }

   /**
    * 根据value从一个set中查询,是否存在
    *
    * @param key   键
    * @param value 值
    * @return true 存在 false不存在
    */
   public boolean sHasKey(String key, Object value) {
     try {
       return redisTemplate.opsForSet().isMember(key, value);
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 将数据放入set缓存
    *
    * @param key    键
    * @param values 值 可以是多个
    * @return 成功个数
    */
   public long sSet(String key, Object... values) {
     try {
       return redisTemplate.opsForSet().add(key, values);
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }

   /**
    * 将set数据放入缓存
    *
    * @param key    键
    * @param time   时间(秒)
    * @param values 值 可以是多个
    * @return 成功个数
    */
   public long sSetAndTime(String key, long time, Object... values) {
     try {
       Long count = redisTemplate.opsForSet().add(key, values);
       if (time > 0)
         expire(key, time);
       return count;
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }

   /**
    * 获取set缓存的长度
    *
    * @param key 键
    * @return
    */
   public long sGetSetSize(String key) {
     try {
       return redisTemplate.opsForSet().size(key);
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }

   /**
    * 移除值为value的
    *
    * @param key    键
    * @param values 值 可以是多个
    * @return 移除的个数
    */
   public long setRemove(String key, Object... values) {
     try {
       Long count = redisTemplate.opsForSet().remove(key, values);
       return count;
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }
   // ===============================List(列表)=================================

   /**
    * 获取list缓存的内容
    *
    * @param key   键
    * @param start 开始
    * @param end   结束 0 到 -1代表所有值
    * @return
    */
   public List  lGet(String key, long start, long end) {
     try {
       return redisTemplate.opsForList().range(key, start, end);
     } catch (Exception e) {
       e.printStackTrace();
       return null;
     }
   }

   /**
    * 获取list缓存的长度
    *
    * @param key 键
    * @return
    */
   public long lGetListSize(String key) {
     try {
       return redisTemplate.opsForList().size(key);
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }

   /**
    * 通过索引 获取list中的值
    *
    * @param key   键
    * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
    * @return
    */
   public Object lGetIndex(String key, long index) {
     try {
       return redisTemplate.opsForList().index(key, index);
     } catch (Exception e) {
       e.printStackTrace();
       return null;
     }
   }

   /**
    * 将list放入缓存
    *
    * @param key   键
    * @param value 值
    * @return
    */
   public boolean lSet(String key, Object value) {
     try {
       redisTemplate.opsForList().rightPush(key, value);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 将list放入缓存
    *
    * @param key   键
    * @param value 值
    * @param time  时间(秒)
    * @return
    */
   public boolean lSet(String key, Object value, long time) {
     try {
       redisTemplate.opsForList().rightPush(key, value);
       if (time > 0)
         expire(key, time);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 将list放入缓存
    *
    * @param key   键
    * @param value 值
    * @return
    */
   public boolean lSet(String key, List  value) {
     try {
       redisTemplate.opsForList().rightPushAll(key, value);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 将list放入缓存
    *
    * @param key   键
    * @param value 值
    * @param time  时间(秒)
    * @return
    */
   public boolean lSet(String key, List  value, long time) {
     try {
       redisTemplate.opsForList().rightPushAll(key, value);
       if (time > 0)
         expire(key, time);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 根据索引修改list中的某条数据
    *
    * @param key   键
    * @param index 索引
    * @param value 值
    * @return
    */
   public boolean lUpdateIndex(String key, long index, Object value) {
     try {
       redisTemplate.opsForList().set(key, index, value);
       return true;
     } catch (Exception e) {
       e.printStackTrace();
       return false;
     }
   }

   /**
    * 移除N个值为value
    *
    * @param key   键
    * @param count 移除多少个
    * @param value 值
    * @return 移除的个数
    */
   public long lRemove(String key, long count, Object value) {
     try {
       Long remove = redisTemplate.opsForList().remove(key, count, value);
       return remove;
     } catch (Exception e) {
       e.printStackTrace();
       return 0;
     }
   }
 }
 
  

具体测试部分就省略了。

源代码下载


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 方法中的情况:

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

有时全合并几条命令发送

但跟完全 pipeline 的方式不同, 测试多次, 但没发现有一次是完整 pipeline 的
复制代码

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

Lettuce 接入

单机版

配置文件

    host: 192.168.131.118
    port: 4884
    password: dsgs548
    database: 0
    # lettuce简单配置
    lettuce:
      pool:
        # 最大活跃链接数 默认8
        max-active: 5
        # 最大空闲连接数 默认8
        max-idle: 10
        # 最小空闲连接数 默认0
        min-idle: 0
复制代码

redis配置类

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
}
复制代码

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

集群版+多数据源

配置文件

spring:
  redis:
    cluster:
      nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885
#      nodes:
#        - 192.168.131.118:4883
#        - 1192.168.131.118:4884
#        - 192.168.131.118:4885
    password: adfafsas
    lettuce:
      pool:
        # 最大活跃链接数 默认8
        max-active: 5
        # 最大空闲连接数 默认8
        max-idle: 10
        # 最小空闲连接数 默认0
        min-idle: 0
  secondaryRedis:
    cluster:
      nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885
#      nodes:
#        - 192.168.131.118:4883
#        - 192.168.131.118:4884
#        - 192.168.131.118:4885
    password: advfafasfsa
复制代码

redis配置类

@Configuration
public class RedisConfig {

    @Autowired
    private Environment environment;
    /**
     * 配置lettuce连接池
     *
     * @return
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.redis.cluster.lettuce.pool")
    public GenericObjectPoolConfig redisPool() {
        return new GenericObjectPoolConfig();
    }

    /**
     * 配置第一个数据源的
     *
     * @return
     */
    @Bean("redisClusterConfig")
    @Primary
    public RedisClusterConfiguration redisClusterConfig() {

        Map source = new HashMap<>(8);
        source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));
        RedisClusterConfiguration redisClusterConfiguration;
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
        return redisClusterConfiguration;

    }


    /**
     * 配置第一个数据源的连接工厂
     * 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory
     *
     * @param redisPool
     * @param redisClusterConfig
     * @return
     */
    @Bean("lettuceConnectionFactory")
    @Primary
    public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
        return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);
    }


    /**
     * 配置第一个数据源的RedisTemplate
     * 注意:这里指定使用名称=factory 的 RedisConnectionFactory
     * 并且标识第一个数据源是默认数据源 @Primary
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean("redisTemplate")
    @Primary
    public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        return getRedisTemplate(redisConnectionFactory);

    }




    /**
     * 配置第二个数据源
     *
     * @return
     */
    @Bean("secondaryRedisClusterConfig")
    public RedisClusterConfiguration secondaryRedisConfig() {

        Map source = new HashMap<>(8);
        source.put("spring.redis.cluster.nodes", environment.getProperty("spring.secondaryRedis.cluster.nodes"));
        RedisClusterConfiguration redisClusterConfiguration;
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));

        return redisClusterConfiguration;
    }

    @Bean("secondaryLettuceConnectionFactory")
    public LettuceConnectionFactory secondaryLettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("secondaryRedisClusterConfig")RedisClusterConfiguration secondaryRedisClusterConfig) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
        return new LettuceConnectionFactory(secondaryRedisClusterConfig, clientConfiguration);
    }

    /**
     * 配置第一个数据源的RedisTemplate
     * 注意:这里指定使用名称=factory2 的 RedisConnectionFactory
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean("secondaryRedisTemplate")
    public RedisTemplate secondaryRedisTemplate(@Qualifier("secondaryLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        return getRedisTemplate(redisConnectionFactory);
    }



    private RedisTemplate getRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

}



参考:

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

你可能感兴趣的:(Java,SpringBoot,Redis/Codis)