一、Redis Sentinel集群解决方案简介
Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自懂切换。
=》不时地监控redis是否按照预期良好地运行;
=》如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);
=》能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
=》sentinel采用投票方式进行故障切换,而投票方式下原则是少数服从多数,所以sentinel一般至少是3台
Sentinel 机制与用法(参考):Redis Sentinel机制与用法
二、redis server sentinel服务端配置(windows下演示)
2.1 下载Redis
参考该地址教程下载配置:http://www.runoob.com/redis/redis-install.html
2.2 HA配置
我们采用一主(master)二从(slave)三sentinel的架构模式来做演示
2.3、新建和修改配置文件
1、修改redis.conf配置文件
由于我们采用的是一主二从三sentinel的模式,所以我们需要6个配置文件,拷贝2份redis.conf配置文件,分别命名为redis6380.conf和redis6381.conf,其中修改redis.conf配置文件的如下几个参数:
修改redis6380.conf配置文件的如下几个参数:
同理修改redis6381.conf配置文件
2、redis.conf同目录下创建并修改sentinel.conf
该模式使用了3sentinel,所以我们需要复制3份sentinel.conf配置文件,并分别命名为sentinel26479.conf和sentinel26579.conf,其中修改sentinel.conf配置文件中的如下几个参数:
同理,修改另外的两个配置文件
配置文件说明:
2.4、启动服务器
1、打开不同的cmd窗口,分别启动master,slave1,slave2
启动命令分别如下:
2、分别启动sentinel1,sentinel2,sentinel3
启动命令分别如下:
服务启动成功后,界面显示如下:
3、查看redis服务器状态
4、查看sentinel的状态
2.5、测试服务器
这里只做简单的测试
2.6、redis主从自动failover测试
1、停止master服务器
2、查看剩余服务器的状态
从上图中可以看出来,master的服务器端口从6379变成了6380,也就是说redis自动的实现了主从切换,我们可以在查看下sentinel的状态,如下:
我们发现sentinel监控到127.0.0.1:6379已经无法ping通了,切换master服务器为127.0.0.1:6380,经过以上测试,说明集群模式已经正常运行,我们可以开始客户端代码配置了。
三、配置客户端(Spring项目)
3.1pom配置
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>4.3.13.RELEASE</springframework.version>
<redis.version>2.9.0</redis.version>
<spring.redis.version>2.0.2.RELEASE</spring.redis.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-beanutils.version>1.9.0</commons-beanutils.version>
<commons-collections.version>3.2.1</commons-collections.version>
<commons-io.version>2.4</commons-io.version>
<commons-logging.version>1.1.1</commons-logging.version>
<commons-codec.version>1.10</commons-codec.version>
<slf4j.version>1.7.7</slf4j.version>
<log4j.version>1.2.16</log4j.version>
<logback.version>1.1.2</logback.version>
</properties>
<repositories>
<!-- 有了仓库的组的概念, 我们只需要做一次引用就可以了 -->
<!-- Nexus中预设了2个仓库组,public repositories和public snapshot repositories -->
<repository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://mvnrepository.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<!--logger begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>oro</groupId>
<artifactId>oro</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- logger end -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
3.2 application.properties配置
#\u6700\u5927\u5206\u914D\u7684\u5BF9\u8C61\u6570
redis.pool.maxActive=1024
#\u6700\u5927\u80FD\u591F\u4FDD\u6301idel\u72B6\u6001\u7684\u5BF9\u8C61\u6570
redis.pool.maxIdle=200
#\u5F53\u6C60\u5185\u6CA1\u6709\u8FD4\u56DE\u5BF9\u8C61\u65F6\uFF0C\u6700\u5927\u7B49\u5F85\u65F6\u95F4
redis.pool.maxWait=1000
#\u5F53\u8C03\u7528borrow Object\u65B9\u6CD5\u65F6\uFF0C\u662F\u5426\u8FDB\u884C\u6709\u6548\u6027\u68C0\u67E5
redis.pool.testOnBorrow=true
redis.pool.testWhileIdle=true
redis.pool.timeBetweenEvictionRunsMillis=60000
#IP
redis.ip=127.0.0.1
#Port
redis.port=6379
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd"
default-autowire="byName">
<!--以下为sentinel 多redis配置 -->
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:application.properties" />
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="2048" />
<property name="maxIdle" value="200" />
<property name="numTestsPerEvictionRun" value="1024" />
<property name="minEvictableIdleTimeMillis" value="-1" />
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<property name="maxWaitMillis" value="1500" />
<property name="testOnReturn" value="false" />
<property name="jmxEnabled" value="true" />
<property name="blockWhenExhausted" value="false" />
<!-- 三个配置配合设置,可以起到以下三点作用: (1)、保证每次获取的连接都是可用的 (2)、定时清理失效连接,补充新连接,提高效率 -->
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
</bean>
<!-- 采用sentinel模式的redis:
redis server sentinel配置:http://blog.csdn.net/liuchuanhong1/article/details/53206028
此处只需要配置master节点名称以及三个sentinel(哨兵)即可,JedisSentinelPool.initSentinels执行时,
遍历多个sentinels,一个一个连接到sentinel,去询问关于masterName的消息,
可以看到是通过jedis.sentinelGetMasterAddrByName()方法去连接sentinel,并询问当前的master的地址
参考地址:https://segmentfault.com/a/1190000002690506
-->
<bean id="redisSentinelConfiguration"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<!--master名称 sentinel.conf里面配置的主节点名称:
name的值必须和redis server端sentinel(sentinel.conf)配置的值一致
-->
<property name="name" value="redis-sentinel" />
</bean>
</property>
<!--此处为sentinel(哨兵配置,非redis server配置) -->
<!-- sentinel为投票模式,至少设置三台reis -->
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="26379" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="26479" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode ">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="26579" />
</bean>
</set>
</property>
</bean>
<bean id="sentinelJedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="sentinelJedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<!-- <property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property> -->
</bean>
</beans>
3.4 实体类
public class User implements Serializable {
private static final long serialVersionUID = -1267719235225203410L;
private String uid;
private String address;
private String mobile;
private String postCode;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String postCode) {
this.postCode = postCode;
}
@Override
public String toString() {
return "User [uid=" + uid + ", address=" + address + ", mobile="
+ mobile + ", postCode=" + postCode + "]";
}
}
3.5 Redis-Sentinel集群测试类
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
/**
* 目前redis并未实现集群技术,redis3.0之后版本可能出现集群技术,拭目以待
*
* spring-data-redis已经对jedis、jredis进行了封装,
* (1)完美兼容redis sentinel部署模式、
* --sentinel采用投票方式进行故障切换,而投票方式下原则是少数服从多数,所以sentinel一般至少是3台
* --参考:https://www.cnblogs.com/aliyunblogs/p/5728861.html
* --Redis-Sentinel作为官方推荐的HA解决方案:https://segmentfault.com/a/1190000002690506
* --redis server sentinel配置:http://blog.csdn.net/liuchuanhong1/article/details/53206028
* --在测试sentinel集群之前,需要在redis服务端进行sentinel模式配置
* (2)兼容redis-cluster部署模式
* (3)单节点redis部署模式,
* 提供统一的API(RedisTemplate)来调用不同的部署模式,完全不用担心redis部署模式变化导致redis客户端代码做调整
*
* 参考案例:http://aperise.iteye.com/blog/2342615
*
* sentinel配置顺序:
* 1、配置redis server sentinel,参考:http://blog.csdn.net/liuchuanhong1/article/details/53206028
* 2、配置java项目:http://aperise.iteye.com/blog/2342615
* 此种方式下搭建的sentinel集群(主从方式),能够实现故障切换;故障节点修复重启后,也可以自动融入集群,作为master的从数据库
*
* @Description:
* @Create: 2017年12月28日 下午3:50:01
* @Modification History
* @Date Author Version Description
*/
public class SpringJedisSentinelTest {
private static ApplicationContext context = null;
private static RedisTemplate<String, User> redisTemplate = null;
static {
context = new ClassPathXmlApplicationContext(
"classpath*:applicationContext-sentinel.xml");
redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
}
public static void main(String[] args) {
User user = new User();
user.setUid("11112222322233");
user.setAddress("上海市");
user.setMobile("mobile");
user.setPostCode("postcode");
save(user);
System.out.println(read(user));
}
public static void save(User user) {
redisTemplate.opsForValue().set(user.getUid(), user);
}
public static User read(User user) {
return redisTemplate.opsForValue().get(user.getUid());
}
public static void listSave(User user) {
List<User> users = new ArrayList<User>();
users.add(user);
redisTemplate.opsForList().leftPushAll(
"user.list.ops." + user.getUid(), users);
}
public static void listRead(User user) {
List<User> users = redisTemplate.opsForList().range(
"user.list.ops." + user.getUid(), 0, -1);
for (Iterator iterator = users.iterator(); iterator.hasNext();) {
User user1 = (User) iterator.next();
System.out.println(user1);
}
}
public static void hashSave(User user) {
Map<String, String> map = new HashMap<String, String>();
map.put("address", user.getAddress());
map.put("mobile", user.getMobile());
map.put("postCode", user.getPostCode());
redisTemplate.opsForHash()
.putAll("user.hash.ops." + user.getUid(), map);
}
public static void hashRead(User user) {
Map<Object, Object> map = redisTemplate.opsForHash().entries(
"user.hash.ops." + user.getUid());
Iterator<Entry<Object, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Object> type = it.next();
System.out.println("key:" + type.getKey() + ",value:"
+ type.getValue());
}
}
public static void setSave(User user) {
/*
* 竟然没有批量新增方法????
*/
redisTemplate.opsForSet().add("user.set.ops." + user.getUid(), user);
}
public static void setRead(User user) {
Set<User> sets = redisTemplate.opsForSet().members(
"user.set.ops." + user.getUid());
for (Iterator iterator = sets.iterator(); iterator.hasNext();) {
User user1 = (User) iterator.next();
System.out.println("user1:" + user1);
}
}
public static void zsetSave(User user) {
Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
zsets.add(tuple0);
redisTemplate.opsForZSet().add("user.zset.ops." + user.getUid(), zsets);
}
public static void zsetRead(User user) {
Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
zsets.add(tuple0);
Set<User> users=redisTemplate.opsForZSet().range("user.zset.ops." + user.getUid(), 0, -1);
for (Iterator iterator = users.iterator(); iterator.hasNext();) {
User user2 = (User) iterator.next();
System.out.println("user2:"+user2);
}
}
}
以上便是Sentinel集群的简单实现了,大家可以按demo跑一跑,然后查一下集群的各个节点数据有没有同步,如果没问题,那你就成功了,兄弟我也在研究阶段,各种百度学习,如果有不读的地方,大家可以留言提醒下。
项目代码地址: https://github.com/TaavettiTao/Redis.git