SpringMVC集成redis cluster集群模式
背景:
最近分配到任务,将原有的redis的哨兵模式升级为集群模式,目前升级工作已正常上线,临近春节,手上的任务不多,所以做一个SpringMVC集成redis cluster集群模式的总结记录,首先先简单介绍一下redis集群的几种模式
1、单机版 不解释
2、Sentinel 哨兵模式
3、Redis Cluster Redis官方集群方案
4、Redis Sharding集群
下面我将详细介绍SpringMVC 如何集成Redis Cluster集群模式,后期如有需要会继续更新哨兵模式。
Redis Cluster 集群模式介绍:
1、cluster相对于哨兵模式是去中心化的,它的每个节点都存储了其它集群的信息,因此每个节点都可以做为集群的中心,容错能力强,具有更高的可用性和在线扩容能力。
2、简单的说,他就是将key通过hash算法,然后进行取模,将不同的key存储到对应的节点上,每个节点又可以有自己的从节点,也有一些局限性:
mget,mset 命令不支持(客户端可能提供了兼容的操作方法,但不建议使用)
pipeline 管道操作去掉,改成单次操作
watch,multi,exec 事务操作过程中,要保证只能对同一个key操作
ok 话不多说,让我们开始进入正题
1、在Maven的pom.xml文件中,增加redis的引用
<!-- redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.0.RELEASE</version>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
注意:
首先spring-data-redis必须是1.7.0以上版本才行!以前用的版本低,启动时报错java.lang.ClassNotFoundException:org.springframework.data.redis.connection.RedisClusterConfiguration。
同时要配置redis.clients,spring-data-redis低版本中是自带的,不需要另外引入,否则报错java.lang.ClassNotFoundException:redis.clients.jedis.JedisPoolConfig
查看步骤截图如下:
2. 配置redis.properties
#redis
#redis.host = 127.0.0.1
#redis.port = 6379
redis.pass = 集群的密码
redis.timeout = 7200
redis.database = 9
redis.pool.maxActive = 1000
redis.pool.minIdle = 0
redis.pool.maxIdle = 200
redis.pool.maxWait = 10000
redis.pool.testWhileIdle = true
redis.pool.testOnBorrow = true
redis.pool.testOnReturn = true
redis.pool.minEvictableIdleTimeMillis = 60000
redis.pool.timeBetweenEvictableRunsMillis = 30000
redis.expire.seconds =18000
spring.redis.cluster.nodes=10.12.123.21:9600,10.12.123.21:9601,10.12.123.21:9602,10.12.123.21:9603,10.12.123.21:9604,10.12.123.21:9605
3. 配置spring-redis.xml
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Jedis连接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- <property name="maxActive" value="1024" /> -->
<!-- <property name="maxWait" value="1000" /> -->
<property name="maxTotal" value="1024" />
<property name="maxIdle" value="200" />
<property name="MaxWaitMillis" value="1000" />
<property name="testOnBorrow" value="true" />
</bean>
<!-- redis集群配置 集群模式 -->
<bean class="org.springframework.data.redis.connection.RedisClusterConfiguration" id="redisClusterConfig">
<property name="maxRedirects" value="6"></property>
<property name="clusterNodes">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19001"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19002"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19003"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19004"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19005"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="10.12.123.21"></constructor-arg>
<constructor-arg name="port" value="19006"></constructor-arg>
</bean>
</set>
</property>
</bean>
<!-- jedis连接工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="password" value="你的集群密码" />
<property name="database" value="0" />
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
<constructor-arg name="clusterConfig" ref="redisClusterConfig"/>
<property name="timeout" value="5000" />
</bean>
<!-- spring jedis template -->
<bean id="template" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory">
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
</bean>
<!-- redis缓存管理 -->
<bean id="redisCacheManager" class="com.your.bangyan.common.cache.RedisCacheManager">
<property name="template" ref="template"></property>
</bean>
</beans>
把spring-redis.xml导入到主配置文件中
<import resource="spring-redis.xml"/>
4. 调用Redis
public class RedisCacheManager extends WebApplicationObjectSupport implements InitializingBean{
private volatile static RedisCacheManager redisManager;
private JedisCluster jedisCluster;
private static final Long RELEASE_SUCCESS = 1L;
/**
* 静态方法,来返回一个实例
* 如果存在则返回
*
* @return
*/
@SuppressWarnings("resource")
public static RedisCacheManager getInstance() {
if (redisManager == null) {
synchronized (RedisCacheManager.class) {
if (redisManager == null) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-redis.xml");
redisManager = (RedisCacheManager) context.getBean("redisCacheManager");
}
}
}
return redisManager;
}
/**
* 说明:redisTemplate获取锁
* @param lockId
* @param millisecond
* @return
*
*/
public boolean getLock(String lockId, long millisecond) {
Long success = jedisCluster.setnx(lockId.getBytes(), String.valueOf(millisecond).getBytes());
if (success==1) {
return true;
} else {
byte[] value = jedisCluster.get(lockId.getBytes());
if (value!=null && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
// 如果锁已经过期
byte[] oldValue = jedisCluster.getSet(lockId.getBytes(), String.valueOf(System.currentTimeMillis() + millisecond + 1).getBytes());
// 防止死锁
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
}
public boolean setLock(String lockId, String lockValue) {
Long success = jedisCluster.setnx(lockId.getBytes(), lockValue.getBytes());
if (success==1) {
return true;
}
return false;
}
/**
* 说明:jedis获取锁
* @param key
* @param value
* @param lockExpireTimeOut
* @param lockWaitTimeOut
* @return
*
*/
public boolean lock(JedisCluster jedisCluster,String key, String value, Long lockExpireTimeOut,Long lockWaitTimeOut) throws Exception {
Long deadTimeLine = System.currentTimeMillis() + lockWaitTimeOut;
boolean flag = true;
for (;;) {
Long result = jedisCluster.setnx(key, value);
if(flag){
jedisCluster.expire(key,lockExpireTimeOut.intValue());
}
flag=false;
if (result==1) {
return true;
}
long lockWaitTimeOutR = deadTimeLine - System.currentTimeMillis();
if (lockWaitTimeOutR <= 0L) {
return false;
}
}
};
/**
* 说明:redisTemplate删除锁
* @param key
*
*/
public void delete(String key) {
jedisCluster.del(key);
}
/**
* 说明:jedis释放锁
* @param key
* @param value
* @return
* @throws Exception
*
*/
public boolean unlock(String key, String value) throws Exception {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
logger.error("jedisCluster信息:"+jedisCluster);
Object result = jedisCluster.evalsha(script, Collections.singletonList(key), Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
/* String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, Collections.singletonList(key),
Collections.singletonList(value));
if ("1".equals(result)) {
return true;
}
return false;*/
}
/**
* 获得jedis连接
* @return
* @throws IOException
*/
public JedisCluster getJedisCluster() throws IOException {
try {
jedisCluster = CIPRedisUtils.getJedisResource(JedisCluster.class);
} catch (Exception e) {
e.printStackTrace();
}
return jedisCluster;
}
/*
* @param timeout 过期时间,当大于0时设置过期时间
*/
private void expire(String key, long timeout) {
if (timeout > 0) {
jedisCluster.pexpire(key,timeout);
}
}
public void setString(String key, String value, long timeout) {
jedisCluster.set(key,value);
expire(key, timeout);
}
public void setString(String key, String value) {
jedisCluster.set(key,value);
}
public String getString(String key) {
return jedisCluster.get(key);
}
public void setList(String key, List<String> value, long timeout) {
for(String str:value){
jedisCluster.lpush(key, str);
}
expire(key, timeout);
}
public void setList(String key, List<String> value) {
for(String str:value){
jedisCluster.lpush(key, str);
}
}
public List<String> getList(String key) {
return jedisCluster.lrange(key, 0, -1);
}
/**
* TODO 未来使用hash存储
* @param key
* @param value
*/
public void setMap(String key, Map<String, Object> value) {
//hashOperatins.putAll(key, value);
String valueStr = JSON.toJSONString(value);
jedisCluster.set(key,valueStr);
}
/**
* TODO 未来使用hash存储
* @param key
* @return
*/
public Map<String, Object> getMap(String key) {
//return hashOperatins.entries(key);
String valueStr = jedisCluster.get(key);
Map<String, Object> map = (Map<String, Object>)JSON.parseObject(valueStr);
return map;
}
/**
* 移除对应key的缓存
*
* @param key
*/
public void removeCache(String key) {
if (jedisCluster.exists(key)) {
jedisCluster.del(key);
}
}
/**
* 获取当前缓存数量
* */
public long getRedisDBCount() {
return jedisCluster.dbSize();
}
/**
* 清空当前缓存
* */
public void clearCurrentRedisDB() {
jedisCluster.flushDB();
}
@Override
public void afterPropertiesSet() throws Exception {
try {
jedisCluster = CIPRedisUtils.getJedisResource(JedisCluster.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上结束后就可以在想要使用的地方直接调用对应的方法啦
注意:Redis 3.0以上的才支持集群操作,而且集群之后,不能再分库了!集群设置密码的时候,也要保证所有的节点密码是一样的,不然会跳转失败而找不到数据!但是在程序中设置密码连接的时候却一直失败,所以最好还是使用内网地址作为连接IP,不设置密码了。以上就是整个流程了,虽然看似简单,但是对于一路踩着坑过来的我来说,也是不容易的,如果你有不同的看法欢迎留言沟通,对你有帮助的话加个关注点个赞吧~。