在springboot中引入spring-boot-starter-data-redis依赖时,默认使用的时Lettuce,有时可能我们不想使用Lettuce而是使用Jedis来操作redis,这就需要我们在引入spring-boot-starter-data-redis依赖时做一些额外的依赖配置。
添加依赖
<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>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--使用池时需要引入-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
application.yml配置
spring:
redis:
timeout: 6000ms
password:
cluster:
max-redirects: 3 # 获取失败 最大重定向次数
nodes:
- 10.10.2.139:7001
- 10.10.2.139:7002
- 10.10.2.139:7003
- 10.10.2.139:7004
- 10.10.2.139:7005
- 10.10.2.139:7000
jedis:
pool:
max-active: 1000 #连接池最大的连接数,若使用负值表示没有限制
max-wait: 10s #连接池最大阻塞等待时间
max-idle: 100 #最大空闲连接数
min-idle: 10
手动注入
JedisConnectionFacotory从Spring Data Redis 2.0开始已经不推荐直接显示设置连接的信息了,一方面为了使配置信息与建立连接工厂解耦,另一方面抽象出Standalone(RedisStandaloneConfiguration ),Sentinel和RedisCluster(RedisClusterConfiguration)三种模式的环境配置类和一个统一的jedis客户端连接配置类(用于配置连接池和SSL连接),使得我们可以更加灵活方便根据实际业务场景需要来配置连接信息。
@Configuration
public class RedisConfig extends CachingConfigurerSupport{
@Autowired
RedisProperties redisProperties;
@Bean
public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
@Bean(name="factory")
public RedisConnectionFactory factory(JedisPoolConfig jedisPoolConfig){
RedisClusterConfiguration redisClusterConfiguration =
new RedisClusterConfiguration();
List<String> nodeList = redisProperties.getCluster().getNodes();
for(String node : nodeList){
String[] host = node.split(":");
redisClusterConfiguration.addClusterNode(
new RedisNode(host[0],Integer.parseInt(host[1])));
}
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb =
(JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)
JedisClientConfiguration.builder();
//指定jedisPoolConifig来修改默认的连接池构造器(真麻烦,滥用设计模式!)
jpcb.poolConfig(jedisPoolConfig);
//通过构造器来构造jedis客户端配置
JedisClientConfiguration jedisClientConfiguration = jpcb.build();
return new JedisConnectionFactory(redisClusterConfiguration,jedisClientConfiguration);
}
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接数
jedisPoolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive());
//最小空闲连接数
jedisPoolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
//最大空闲连接数
jedisPoolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
//当池内没有可用的连接时,最大等待时间
jedisPoolConfig.setMaxWaitMillis(
redisProperties.getJedis().getPool().getMaxWait().getSeconds()*1000L);
//------其他属性根据需要自行添加-------------
return jedisPoolConfig;
}
}
Redis作为高速缓存数据库,目前应用非常广泛。RedisTemplate是Spring提供用于操作redis数据库的一个类。
将数据存放到Redis中,以及数据读取。这里必然涉及到数据的系列化和反系列化。RedisTemplate默认的系列化类是JdkSerializationRedisSerializer,用JdkSerializationRedisSerializer序列化的话,被序列化的对象必须实现Serializable接口。在存储内容时,除了属性的内容外还存了其它内容在里面,总长度长,且不容易阅读。
我们要求是存储的数据可以方便查看,也方便反系列化,方便读取数据。
JacksonJsonRedisSerializer
和GenericJackson2JsonRedisSerializer
,两者都能系列化成json,但是后者会在json中加入@class属性,类的全路径包名,方便反系列化。前者如果存放了List则在反系列化的时候如果没指定TypeReference则会报错java.util.LinkedHashMap cannot be cast to 。
RedisTemplate里面定义了key,value,hashKey,haskValue等键,值的系列化器,我们可以自己方便的修改。如果没有设置则会有默认的。 JdkSerializationRedisSerializer。
pipeline
Jedis中
JedisCluster
是不支持pipeline操作的,如果使用了redis集群,在spring-boot-starter-data-redis
中又正好用到的pipeline,那么会接收到Pipeline is currently not supported for JedisClusterConnection.
这样的报错。
Lettuce中的pipeline
spring boot 2.0开始,配置spring-boot-starter-data-redis
将不依赖Jedis,而是依赖Lettuce,在Lettuce中,redis cluster使用pipeline不会有问题。
让spring-data-redis也支持pipeline的思路
public Pipeline getPipeline() {
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection redisConnection = factory.getConnection();
JedisClusterConnection jedisClusterConnection = (JedisClusterConnection) redisConnection;
// 获取到原始到JedisCluster连接
JedisCluster jedisCluster = jedisClusterConnection.getNativeConnection();
// 通过key获取到具体的Jedis实例
// 计算hash slot,根据特定的slot可以获取到特定的Jedis实例
int slot = JedisClusterCRC16.getSlot(KEY);
Field field = ReflectionUtils.findField(BinaryJedisCluster.class, null, JedisClusterConnectionHandler.class);
field.setAccessible(true);
JedisSlotBasedConnectionHandler jedisClusterConnectionHandler = null;
try {
jedisClusterConnectionHandler = (JedisSlotBasedConnectionHandler) field.get(jedisCluster);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Jedis jedis = jedisClusterConnectionHandler.getConnectionFromSlot(slot);
// 接下来就是pipeline操作了
Pipeline pipeline = jedis.pipelined();
return pipeline;
}
获取jedis客户端对应的slot
@PostConstruct
public void init() {
RedisConnection redisConnection = factory.getConnection();
JedisClusterConnection jedisClusterConnection = (JedisClusterConnection) redisConnection;
// 获取到原始到JedisCluster连接
JedisCluster jedisCluster = jedisClusterConnection.getNativeConnection();
Set<String> set = jedisCluster.getClusterNodes().keySet();
for(String key :set ){
JedisPool jedisPool = jedisCluster.getClusterNodes().get(key);
Jedis jedis = jedisPool.getResource();
String clusterNodesCommand = jedis.clusterNodes();
String[] allNodes = clusterNodesCommand.split("\n");
for (String allNode : allNodes) {
String[] splits = allNode.split(" ");
String hostAndPort = splits[1];
if(splits[2].contains("myself")){
ClusterNodeObject clusterNodeObject =
new ClusterNodeObject(splits[0], splits[1],
splits[2].contains("master"), splits[3],
splits[7].equalsIgnoreCase("connected"),
splits.length == 9 ? splits[8] : null,jedis);
nodelist.add(clusterNodeObject);
}
}
}
}
String[] allNodes = clusterNodesCommand.split("\n");
获取到的信息
e54b82fd2b5ab238906cff7fc6250a7bc66c6fec 192.168.1.163:6389 master - 0 1469600305090 31 connected 0-5460
166baa38c8ab56339c11f0446257c7a6059a219b 192.168.1.165:6389 slave 1609b090dfaaac702449b72d30b2330521ce2506 0 1469600304588 29 connected
1609b090dfaaac702449b72d30b2330521ce2506 192.168.1.163:6390 master - 0 1469600305592 29 connected 10923-16383
539627a393aa43e82ca8c16d1e935611fec4e709 192.168.1.163:6388 myself,master - 0 0 28 connected 5461-10922
d9b3738ff16e99075242b865a0b6cc137c20d502 192.168.1.165:6390 slave 539627a393aa43e82ca8c16d1e935611fec4e709 0 1469600305090 28 connected
101227d3cb13f08a47ad2afe1b348d0efc3cb3b0 192.168.1.165:6388 slave e54b82fd2b5ab238906cff7fc6250a7bc66c6fec 0 1469600304088 31 connected
cluster nodes 命令的输出有点儿复杂, 它的每一行都是由以下信息组成的:
- 节点 ID :例如 3fc783611028b1707fd65345e763befb36454d73 。
- ip:port :节点的 IP 地址和端口号, 例如 127.0.0.1:7000 , 其中 :0 表示的是客户端当前连接的 IP 地址和端口号。
- flags :节点的角色(例如 master 、 slave 、 myself )以及状态(例如 fail ,等等)。
- 如果节点是一个从节点的话, 那么跟在 flags 之后的将是主节点的节点 ID : 例如 127.0.0.1:7002 的主节点的节点 ID 就是 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 。
- 集群最近一次向节点发送 PING 命令之后, 过去了多长时间还没接到回复。
- 节点最近一次返回 PONG 回复的时间。
- 节点的配置纪元(configuration epoch):详细信息请参考 Redis 集群规范 。
- 本节点的网络连接情况:例如 connected 。
- 节点目前包含的槽:例如 127.0.0.1:7001 目前包含号码为 5960 至 10921 的哈希槽。