Spring整合Redis-sentinel集群实现03

一、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的架构模式来做演示

[java]  view plain  copy
  1. master ip:127.0.0.1 port:6379  
  2. slave1 ip:127.0.0.1 port:6380  
  3. slave2 ip:127.0.0.1 port:6381  

2.3、新建和修改配置文件

1、修改redis.conf配置文件

由于我们采用的是一主二从三sentinel的模式,所以我们需要6个配置文件,拷贝2redis.conf配置文件,分别命名为redis6380.confredis6381.conf,其中修改redis.conf配置文件的如下几个参数:

[java]  view plain  copy
  1. port 6379  
  2. bind 127.0.0.1  

修改redis6380.conf配置文件的如下几个参数:

[java]  view plain  copy
  1. port 6380  
  2. bind 127.0.0.1  
  3. slaveof 127.0.0.1 6379  // 设置master服务器为6379  

同理修改redis6381.conf配置文件

2、redis.conf同目录下创建并修改sentinel.conf

该模式使用了3sentinel,所以我们需要复制3sentinel.conf配置文件,并分别命名为sentinel26479.confsentinel26579.conf,其中修改sentinel.conf配置文件中的如下几个参数:

[java]  view plain  copy
  1. port 26379 // 当前Sentinel服务运行的端口  
  2. sentinel monitor mymaster  127.0.0.1 6379 2   
  3. sentinel down-after-milliseconds mymaster 5000  
  4. sentinel parallel-syncs mymaster  1  
  5. sentinel failover-timeout mymaster  15000  

同理,修改另外的两个配置文件

配置文件说明:

[java]  view plain  copy
  1. 1. port :当前Sentinel服务运行的端口  
  2. 2.sentinel monitor mymaster  127.0.0.1 6379 2:Sentinel去监视一个名为mymaster 的主redis实例,这个主实例的IP地址为本机地址127.0.0.1,端口号为6379,而将这个主实例判断为失效至少需要2个 Sentinel进程的同意,只要同意Sentinel的数量不达标,自动failover就不会执行  
  3. 3.sentinel down-after-milliseconds mymaster 5000:指定了Sentinel认为Redis实例已经失效所需的毫秒数。当 实例超过该时间没有返回PING,或者直接返回错误,那么Sentinel将这个实例标记为主观下线。只有一个 Sentinel进程将实例标记为主观下线并不一定会引起实例的自动故障迁移:只有在足够数量的Sentinel都将一个实例标记为主观下线之后,实例才会被标记为客观下线,这时自动故障迁移才会执行  
  4. 4.sentinel parallel-syncs mymaster 1:指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长  
  5. 5.sentinel failover-timeout mymaster 15000:如果在该时间(ms)内未能完成failover操作,则认为该failover失败  

2.4、启动服务器

1、打开不同的cmd窗口,分别启动masterslave1slave2

启动命令分别如下:

[java]  view plain  copy
  1. redis-server.exe redis.conf  
  2. redis-server.exe redis6380.conf  
  3. redis-server.exe redis6381.conf  

2、分别启动sentinel1sentinel2sentinel3

启动命令分别如下:

[java]  view plain  copy
  1. redis-server.exe sentinel.conf --sentinel  
  2. redis-server.exe sentinel26479.conf --sentinel  
  3. redis-server.exe sentinel26579.conf --sentinel  

服务启动成功后,界面显示如下:


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  


3.3 applicationContext-sentinel.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: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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值