Spring整合redis,通過sentinel進行主從切換。(何志雄)
阿新 • • 發佈:2018-12-25
實現功能描述:
redis伺服器進行Master-slaver-slaver-....主從配置,通過2臺sentinel進行failOver故障轉移,自動切換,採用該程式碼完全可以直接用於實際生產環境。
題外話:
一般來說這樣的部署足以支援數以百萬級的使用者,但如果數量實在是太高,此時redis的Master-Slaver主從不一定能夠滿足,因此進行redis的分片。
本文不講解redis的分片,但如果你使用了,需要注意的按照另一篇文章的介紹:Sentinel&Jedis看上去是個完美的解決方案,這句話只說對了一半,
在無分片的情況是這樣,但我們的應用使用了資料分片-sharing,資料被平均分佈到4個不同的例項上,每個例項以主從結構部署,Jedis沒有提供
基於Sentinel的ShardedJedisPool,也就是說在4個分片中,如果其中一個分片發生主從切換,應用所使用的ShardedJedisPool無法獲得通知,所有
該程式碼模擬多執行緒向redis中set/get。
1、maven依賴配置
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency>
2、redis.properties
# Redis settings #sentinel1的IP和埠 im.hs.server.redis.sentinel1.host=192.168.62.154 im.hs.server.redis.sentinel1.port=26379 #sentinel2的IP和埠 im.hs.server.redis.sentinel2.host=192.168.62.153 im.hs.server.redis.sentinel2.port=26379 #sentinel的鑑權密碼 im.hs.server.redis.sentinel.masterName=155Master im.hs.server.redis.sentinel.password=hezhixiong #最大閒置連線數 im.hs.server.redis.maxIdle=500 #最大連線數,超過此連線時操作redis會報錯 im.hs.server.redis.maxTotal=5000 im.hs.server.redis.maxWaitTime=1000 im.hs.server.redis.testOnBorrow=true #最小閒置連線數,spring啟動的時候自動建立該數目的連線供應用程式使用,不夠的時候會申請。 im.hs.server.redis.minIdle=300
3、spring-redis.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Spring自動將該包目錄下標記為@Service的所有類作為spring的Bean -->
<context:component-scan base-package="com.gaojiasoft.test.redis" />
<context:property-placeholder location="classpath:conf/redis/redis.properties" />
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${im.hs.server.redis.maxTotal}" />
<property name="minIdle" value="${im.hs.server.redis.minIdle}" />
<property name="maxWaitMillis" value="${im.hs.server.redis.maxWaitTime}" />
<property name="maxIdle" value="${im.hs.server.redis.maxIdle}" />
<property name="testOnBorrow" value="${im.hs.server.redis.testOnBorrow}" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />
</bean>
<bean id="sentinelConfiguration"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="${im.hs.server.redis.sentinel.masterName}"></property>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host"
value="${im.hs.server.redis.sentinel1.host}"></constructor-arg>
<constructor-arg name="port"
value="${im.hs.server.redis.sentinel1.port}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host"
value="${im.hs.server.redis.sentinel2.host}"></constructor-arg>
<constructor-arg name="port"
value="${im.hs.server.redis.sentinel2.port}"></constructor-arg>
</bean>
</set>
</property>
</bean>
<bean id="connectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:password="${im.hs.server.redis.sentinel.password}">
<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>
<constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
</beans><strong>
</strong>
4、RedisServiceImpl.java
package com.gaojiasoft.test.redis;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service("redisService")
public class RedisServiceImpl {
private Logger logger = LoggerFactory.getLogger("RedisServiceImpl");
@Autowired
RedisTemplate<?, ?> redisTemplate;
// 執行緒池
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
256, 256, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new BasicThreadFactory.Builder().daemon(true)
.namingPattern("redis-oper-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
public void set(final String key, final String value) {
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
connection.set(
redisTemplate.getStringSerializer().serialize(key),
redisTemplate.getStringSerializer().serialize(value));
logger.debug("save key:" + key + ",value:" + value);
return null;
}
});
}
public String get(final String key) {
return redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection)
throws DataAccessException {
byte[] byteKye = redisTemplate.getStringSerializer().serialize(
key);
if (connection.exists(byteKye)) {
byte[] byteValue = connection.get(byteKye);
String value = redisTemplate.getStringSerializer()
.deserialize(byteValue);
logger.debug("get key:" + key + ",value:" + value);
return value;
}
logger.error("valus does not exist!,key:"+key);
return null;
}
});
}
public void delete(final String key) {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) {
connection.del(redisTemplate.getStringSerializer().serialize(
key));
return null;
}
});
}
/**
* 執行緒池併發操作redis
*
* @param keyvalue
*/
public void mulitThreadSaveAndFind(final String keyvalue) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
set(keyvalue, keyvalue);
get(keyvalue);
} catch (Throwable th) {
// 防禦性容錯,避免高併發下的一些問題
logger.error("", th);
}
}
});
}
}
5、RedisTest.java (Junit測試用例)
package com.gaojiasoft.test.redis;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class RedisTest {
private static ConfigurableApplicationContext context;
RedisServiceImpl service;
@Test
public void testSave() throws InterruptedException {
context = new ClassPathXmlApplicationContext(
"classpath:conf/redis/spring-redis.xml");
service = (RedisServiceImpl) context.getBean("redisService");
int i = 1;
while (true) {
Thread.sleep(1);
try {
service.mulitThreadSaveAndFind("" + i);
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
}
}