Spring整合Redis之集群与故障转移
前言
本文主要讲解spring整合redis集群,关于redis集群搭建网上相关文章挺多的,大家可以自己先搭建好,可以参考官网或中文版官网。本文假设你已经搭建好集群了,笔者redis(版本4.0.1)集群环境如下图:
7000、7001、7002三个主节点,7003、7004、7005三个从节点。Redis集群共有16384个哈希槽(hash slot)用于存放key,当前3个节点哈希槽分布为:
- 节点 7000 包含 0 到 5460号哈希槽
- 节点 7001 包含 5461 到 10922 号哈希槽
- 节点 7002 包含 10923 到 16383 号哈希槽
spring整合redis cluster
step1、maven依赖引入
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.6.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
step2、配置ConnectionFactory
建立集群连接需要获取RedisClusterConnection实例,该实例通过RedisConnectionFactory获取,可以如下配置
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/>
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
</bean>
JedisConnectionFactory是RedisConnectionFactory的实现,构造函数需要两个参数,分别是集群配置clusterConfig和池配置poolConfig,下面一一来看
step3、配置JedisPoolConfig
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="minIdle" value="${redis.minIdle}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />
</bean>
各个属性值从属性文件中读取,需要如下配置
<context:property-placeholder location="classpath:*.properties" ignore-unresolvable="true" />
属性文件spring-redis.properties内容
redis.minIdle=50
redis.maxIdle=200
redis.maxActive=100
redis.maxWait=3000
redis.testOnBorrow=true
step4、配置RedisClusterConfiguration
集群配置有两种方式
1.clusterNodes设值注入
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<property name="clusterNodes">
<set>
<bean name="node1" class="org.springframework.data.redis.connection.RedisClusterNode">
<constructor-arg name="host" value="192.168.48.28"/>
<constructor-arg name="port" value="7000"/>
</bean>
<bean name="node2" class="org.springframework.data.redis.connection.RedisClusterNode">
<constructor-arg name="host" value="192.168.48.28"/>
<constructor-arg name="port" value="7001"/>
</bean>
<bean name="node3" class="org.springframework.data.redis.connection.RedisClusterNode">
<constructor-arg name="host" value="192.168.48.28"/>
<constructor-arg name="port" value="7002"/>
</bean>
</set>
</property>
</bean>
2.propertySource构造注入
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<constructor-arg name="propertySource" ref="propertySource"/>
</bean>
构造函数参数类型为org.springframework.core.env.PropertySource,我们使用它的实现类ResourcePropertySource
<bean name="propertySource" class="org.springframework.core.io.support.ResourcePropertySource">
<constructor-arg name="location" value="classpath:spring-redis-cluster.properties" />
</bean>
spring-redis-cluster.properties内容:
#集群节点host:port,多个节点逗号分隔
spring.redis.cluster.nodes=192.168.48.28:7000,192.168.48.28:7001,192.168.48.28:7002
spring.redis.cluster.max-redirects=5
查看RedisClusterConfiguration源码可以看到这两个配置属性常量
个人比较喜欢第二种配置,更加简洁
step5、配置RedisTemplate
然后配置RedisTemplate,我们用StringRedisTemplate实现
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
step6、测试
通过main方法测试一下
package example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
public class SpringMain {
public static void main(String[] args) throws InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-redis.xml");
StringRedisTemplate template = context.getBean(StringRedisTemplate.class);
ValueOperations<String, String> valueOperations = template.opsForValue();
valueOperations.set("key1","value1");
valueOperations.set("key2","value2");
valueOperations.set("key3","value3");
valueOperations.set("key4","value4");
valueOperations.set("key5","value5");
valueOperations.set("key6","value6");
System.exit(0);
}
}
通过命令行连接redis服务器查看结果:
每个key通过CRC16(详情参考官网)校验后对16384取模放到对应哈希槽(hash slot)中。redis-cli客户端工具会根据哈希槽(hash slot)所在节点进行跳转,所以会看到“-> Redirected to slot…”的提示。
最后贴下完整配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:redis="http://www.springframework.org/schema/redis" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:*.properties" ignore-unresolvable="true" />
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/>
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
</bean>
<!-- RedisClusterConfiguration -->
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<constructor-arg name="propertySource" ref="propertySource"/>
<!-- <property name="clusterNodes"> <set> <bean name="node1" class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.48.28"/> <constructor-arg name="port" value="7000"/> </bean> <bean name="node2" class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.48.28"/> <constructor-arg name="port" value="7001"/> </bean> <bean name="node3" class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.48.28"/> <constructor-arg name="port" value="7002"/> </bean> </set> </property>-->
</bean>
<bean name="propertySource" class="org.springframework.core.io.support.ResourcePropertySource">
<constructor-arg name="location" value="classpath:spring-redis-cluster.properties" />
</bean>
<!-- JedisPoolConfig definition -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="minIdle" value="${redis.minIdle}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />
</bean>
<!-- redis template definition -->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
</beans>
redis集群进阶
故障转移
现在我们来测试下故障转移,通过向端口号为7002的主节点发送DEBUG SEGFAULT命令, 让这个主节点崩溃:
$ redis-cli -p 7002 debug segfault
查看现在集群节点状态:
可以看到7002状态为fail,他的从节点7005被选举为新的主节点
根据前面的测试我们知道键key4被“hash”到了13120哈希槽,位于7002节点,现在7002挂了,他会放到哪呢?
ValueOperations<String, String> valueOperations = template.opsForValue();
valueOperations.set("key4","bar4");
可以看见,13120哈希槽已经转移到新的主节点7005上了
主从复制模型
如果上步新的主节点7005也挂了会出现什么情况呢?我们让这个节点崩溃:
$ redis-cli -p 7005 debug segfault
查看现在集群节点状态:
可以看到7005的状态也为fail,现在通过spring redis发送命令,会报连接异常
Exception in thread "main" org.springframework.data.redis.RedisConnectionFailureException: Could not get a resource from the pool; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:67)
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:41)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:37)
at org.springframework.data.redis.connection.jedis.JedisClusterConnection.convertJedisAccessException(JedisClusterConnection.java:3999)
at org.springframework.data.redis.connection.jedis.JedisClusterConnection.set(JedisClusterConnection.java:642)
at org.springframework.data.redis.connection.DefaultStringRedisConnection.set(DefaultStringRedisConnection.java:744)
at org.springframework.data.redis.core.DefaultValueOperations$10.inRedis(DefaultValueOperations.java:172)
at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:57)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:207)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:91)
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:169)
at example.SpringMain.main(SpringMain.java:26)
再看看通过命令行向集群发送命令:
发现整个集群已经宕掉了,根据redis主从复制模型,当某个主节点和他的所有从节点都挂掉,集群会因为缺少某一范围的哈希槽而不用,如上集群7002主节点7005从节点都挂了,集群因缺少10923-16383范围哈希槽而变的不可用。
参考
spring-data-redis cluster//docs.spring.io/spring-data/redis/docs/2.0.0.RC2/reference/html/\#cluster
中文官网Redis集群教程//www.redis.cn/topics/cluster-tutorial.html
还没有评论,来说两句吧...