redis哨兵

2022-08-12 20:23:44 浏览数 (1)

目录

  • redis安装
  • 哨兵安装
  • java连接哨兵
  • 扩展jredis
  • rdb和aof

redis命令参考

redis安装

redis安装

哨兵安装

注意防火墙,注意防火墙,注意防火墙

  1. 在3台机器部署哨兵,组成一个集群
  2. 修改配置文件
  3. 启动redis 启动哨兵
代码语言:javascript复制
mkdir /etc/sentinal //哨兵配置文件目录 
mkdir -p /var/sentinal/5000 //工作目录 
//配置配置文件进行修改 
cp /root/redis-3.2.8/sentinel.conf /etc/sentinal/ 
protected-mode no 
port 5000 
bind 192.168.31.187 127.0.0.1 
dir /var/sentinal/5000 
sentinel monitor mymaster 192.168.31.187 6379 2 
sentinel down-after-milliseconds mymaster 30000 
sentinel failover-timeout mymaster 60000 
sentinel parallel-syncs mymaster 1 
sentinel auth-pass mymaster 123456 
下面配置在文件中没有 自己新增 
daemonize yes 
logfile "/var/redis/sen5000.log" 
//不同的机器。配置对应得ip地址 
启动 
redis-sentinel /etc/sentinal/5000.conf 

配置解释

代码语言:javascript复制
sentinel monitor master-group-name hostname port quorum 
quorum的解释如下: 
(1)至少多少个哨兵要一致同意,master进程挂掉了,或者slave进程挂掉了,或者要启动一个故障转移操作 
(2)quorum是用来识别故障的,真正执行故障转移的时候,还是要在哨兵集群执行选举,选举一个哨兵进程出来执行故障转移操作 
(3)假设有5个哨兵,quorum设置了2,那么如果5个哨兵中的2个都认为master挂掉了; 2个哨兵中的一个就会做一个选举,选举一个哨兵出来,执行故障转移; 如果5个哨兵中有3个哨兵都是运行的,那么故障转移就会被允许执行 
down-after-milliseconds,超过多少毫秒跟一个redis实例断了连接,哨兵就可能认为这个redis实例挂了 
parallel-syncs,新的master别切换之后,同时有多少个slave被切换到去连接新master,重新做同步,数字越低,花费的时间越多 
假设你的redis是1个master,4个slave 
然后master宕机了,4个slave中有1个切换成了master,剩下3个slave就要挂到新的master上面去 
这个时候,如果parallel-syncs是1,那么3个slave,一个一个地挂接到新的master上面去,1个挂接完,而且从新的master sync完数据之后,再挂接下一个 
如果parallel-syncs是3,那么一次性就会把所有slave挂接到新的master上去 
failover-timeout,执行故障转移的timeout超时时长 
哨兵相互发现 
每个哨兵都能去监控到对应的redis master,并能够自动发现对应的slave 
哨兵之间,互相会自动进行发现,用的就是之前说的pub/sub,消息发布和订阅channel消息系统和机制 

java连接哨兵

spring.xml

代码语言:javascript复制
<?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:mvc="http://www.springframework.org/schema/mvc" 
xmlns:tx="http://www.springframework.org/schema/tx" 
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.0.xsd 
http://www.springframework.org/schema/mvc 
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-4.0.xsd"> 
<!-- <context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true"/> 
--> 
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/> 
<bean id="environmentVariablesConfiguration" 
class="org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig"> 
<property name="algorithm" value="PBEWithMD5AndDES" /> 
<property name="password" value="123456" /> 
<!--<property name="passwordEnvName" value="APP_ENCRYPTION_PASSWORD" /> --> 
</bean> 
<bean id="configurationEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor"> 
<property name="config" ref="environmentVariablesConfiguration" /> 
</bean> 
<bean id="propertyConfigurer" 
class="org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer"> 
<constructor-arg ref="configurationEncryptor" /> 
<property name="locations"> 
<list> 
<value>classpath:jdbc.properties</value> 
</list> 
</property> 
</bean> 
<!-- 扫描文件(自动将servicec层注入) --> 
<!-- controller在mvc里面配置 service 要在spring里面配置 mvc属于子容器没有事务的能力--> 
<context:component-scan base-package="com.ding.service"/> 
<!-- 1. 数据源 : DriverManagerDataSource --> 
<bean id="dataSource" 
class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
<property name="driverClassName" value="com.mysql.jdbc.Driver"/> 
<property name="url" value="jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8"/> 
<property name="username" value="${jdbc.username}"/> 
<!--<property name="password" value="${jdbc.password}"/>--> 
</bean> 
<!-- 
2. mybatis的SqlSession的工厂: SqlSessionFactoryBean 
dataSource / typeAliasesPackage 
--> 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
<property name="dataSource" ref="dataSource"/> 
<!-- 当mybatis的xml文件和mapper接口不在相同包下时,需要用mapperLocations属性指定xml文件的路径。 
*是个通配符,代表所有的文件,**代表所有目录下 --> 
<property name="mapperLocations" value="classpath:com/ding/mapper/xml/*.xml" /> 
<property name="plugins"> 
<array> 
<bean class="com.github.pagehelper.PageHelper"> 
<property name="properties"> 
<value>dialect=mysql</value> 
</property> 
</bean> 
</array> 
</property> 
</bean> 
<!-- 
3. mybatis自动扫描加载Sql映射文件 : MapperScannerConfigurer 
sqlSessionFactory / basePackage 
--> 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
<property name="basePackage" value="com/ding/mapper"/> 
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> 
</bean> 
<!-- 4. 事务管理 : DataSourceTransactionManager --> 
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
<property name="dataSource" ref="dataSource"/> 
</bean> 
<!-- 5. 使用声明式事务 --> 
<tx:annotation-driven transaction-manager="txManager" /> 
<!-- redis属性配置 --> 
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> 
<property name="maxTotal" value="${redis.pool.maxTotal}" /> 
<property name="maxIdle" value="${redis.pool.maxIdle}" /> 
<property name="numTestsPerEvictionRun" value="${redis.pool.numTestsPerEvictionRun}" /> 
<property name="timeBetweenEvictionRunsMillis" value="${redis.pool.timeBetweenEvictionRunsMillis}" /> 
<property name="minEvictableIdleTimeMillis" value="${redis.pool.minEvictableIdleTimeMillis}" /> 
<property name="softMinEvictableIdleTimeMillis" value="${redis.pool.softMinEvictableIdleTimeMillis}" /> 
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> 
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" /> 
</bean> 
<!-- redis集群配置 哨兵模式 --> 
<bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> 
<property name="master"> 
<bean class="org.springframework.data.redis.connection.RedisNode"> 
<!--这个值要和Sentinel中指定的master的值一致,不然启动时找不到Sentinel会报错的--> 
<property name="name" value="${redis.groupname}"></property> 
</bean> 
</property> 
<!--记住了,这里是指定Sentinel的IP和端口,不是Master和Slave的--> 
<property name="sentinels"> 
<set> 
<bean class="org.springframework.data.redis.connection.RedisNode"> 
<constructor-arg name="host" value="${redis.sentinel.host1}"></constructor-arg> 
<constructor-arg name="port" value="${redis.sentinel.port1}"></constructor-arg> 
</bean> 
<bean class="org.springframework.data.redis.connection.RedisNode"> 
<constructor-arg name="host" value="${redis.sentinel.host2}"></constructor-arg> 
<constructor-arg name="port" value="${redis.sentinel.port2}"></constructor-arg> 
</bean> 
<bean class="org.springframework.data.redis.connection.RedisNode"> 
<constructor-arg name="host" value="${redis.sentinel.host3}"></constructor-arg> 
<constructor-arg name="port" value="${redis.sentinel.port3}"></constructor-arg> 
</bean> 
</set> 
</property> 
</bean> 
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" id="jedisConnectionFactory"> 
<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg> 
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> 
<property name="password" value="${redis.password}"></property> 
</bean> 
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" id="stringRedisSerializer"></bean> 
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> 
<property name="connectionFactory" ref="jedisConnectionFactory"></property> 
<property name="keySerializer" ref="stringRedisSerializer"></property> 
<property name="valueSerializer" ref="stringRedisSerializer"></property> 
</bean> 
</beans> 

pom.xml

代码语言:javascript复制
<!-- redis start--> 
<dependency> 
<groupId>org.springframework.data</groupId> 
<artifactId>spring-data-redis</artifactId> 
<version>1.5.2.RELEASE</version> 
</dependency> 
<dependency> 
<groupId>redis.clients</groupId> 
<artifactId>jedis</artifactId> 
<version>2.9.0</version> 
</dependency> 
<!-- redis end--> 

redis.properties

代码语言:javascript复制
redis.groupname=mymaster 
redis.password=123456 
redis.sentinel.host1=192.168.144.3 
redis.sentinel.port1=5000 
redis.sentinel.host2=192.168.144.4 
redis.sentinel.port2=5000 
redis.sentinel.host3=192.168.144.8 
redis.sentinel.port3=5000 
redis.pool.maxTotal=1024 
redis.pool.maxIdle=200 
redis.pool.maxWaitMillis=1000 
redis.pool.testOnBorrow=true 
redis.pool.timeBetweenEvictionRunsMillis=30000 
redis.pool.minEvictableIdleTimeMillis=30000 
redis.pool.softMinEvictableIdleTimeMillis=10000 
redis.pool.numTestsPerEvictionRun=1024 
#1000*60*60*1 
redis.pool.expire=3600000 
redis.pool.unlock=false 

结果

代码语言:javascript复制
master故障能自动转移 
但是JedisSentinel 读写都是连接master,没有读写分离 
slave只能用于故障转移 
需要读写分离要自己扩展JedisSentinel 
注意: 
密码 没密码可以不要 

扩展jredis

由于java连接哨兵模式,但是每次连接的都是master(不管读写) 没有做到读写分离。需要自己扩展jredis来支持读写分离

参考 https://www.cnblogs.com/moonandstar08/p/7482143.html https://www.jack-yin.com/coding/spring-boot/2683.html

注意::验证方法,这里都shi

//RedisTemplate

代码语言:javascript复制
<bean class="com.ding.utils.TWReadOnlyJedisConnectionFactory" id="jedisSentinelSlaveConnectionFactory"> 
<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg> 
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> 
<property name="password" value="123456"></property> 
</bean> 
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" id="stringRedisSerializer"></bean> 
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> 
<property name="connectionFactory" ref="jedisSentinelSlaveConnectionFactory"></property> 
<property name="keySerializer" ref="stringRedisSerializer"></property> 
<property name="valueSerializer" ref="stringRedisSerializer"></property> 
</bean> 

TWReadOnlyJedisConnectionFactory.java

代码语言:javascript复制
package com.ding.utils; 
import org.apache.commons.collections.CollectionUtils; 
import org.springframework.data.redis.connection.RedisNode; 
import org.springframework.data.redis.connection.RedisSentinelConfiguration; 
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 
import org.springframework.util.ReflectionUtils; 
import redis.clients.jedis.Jedis; 
import redis.clients.jedis.JedisPoolConfig; 
import redis.clients.jedis.JedisShardInfo; 
import redis.clients.util.Pool; 
import java.lang.reflect.Method; 
import java.util.Collection; 
import java.util.Collections; 
import java.util.LinkedHashSet; 
import java.util.Set; 
public class TWReadOnlyJedisConnectionFactory extends JedisConnectionFactory { 
private static final Method GET_TIMEOUT_METHOD; 
static { 
Method getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class,"getTimeout"); 
if(null == getTimeoutMethodCandidate) { 
getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class,"getTimeout"); 
} 
GET_TIMEOUT_METHOD=getTimeoutMethodCandidate; 
} 
public TWReadOnlyJedisConnectionFactory() { 
super(); 
} 
public TWReadOnlyJedisConnectionFactory(JedisShardInfo shardInfo) { 
super(shardInfo); 
} 
public TWReadOnlyJedisConnectionFactory(JedisPoolConfig poolConfig){ 
this((RedisSentinelConfiguration)null,poolConfig); 
} 
public TWReadOnlyJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig){ 
this(sentinelConfig,null); 
} 
public TWReadOnlyJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig,JedisPoolConfig poolConfig){ 
super(sentinelConfig,poolConfig); 
} 
@Override 
public void afterPropertiesSet() { 
try { 
super.afterPropertiesSet(); 
}catch(Exception e) { 
} 
} 
protected Pool<Jedis> createRedisSentinelPool(RedisSentinelConfiguration config){ 
JedisSentinelSlavePool pool1 = new JedisSentinelSlavePool(config.getMaster().getName(), 
convertToJedisSentinelSet(config.getSentinels()), 
getPoolConfig()!=null?getPoolConfig():new JedisPoolConfig(), 
getTimeout(), 
getPassword()); 
return pool1; 
} 
private Set<String> convertToJedisSentinelSet(Collection<RedisNode> nodes) { 
if(CollectionUtils.isEmpty(nodes)) { 
return Collections.emptySet(); 
} 
Set<String> convertedNodes = new LinkedHashSet<String>(nodes.size()); 
for(RedisNode node : nodes) 
{ 
convertedNodes.add(node.asString()); 
} 
return convertedNodes; 
} 
private int getTimeOutFrom(JedisShardInfo shardInfo){ 
return (Integer) ReflectionUtils.invokeMethod(GET_TIMEOUT_METHOD,shardInfo); 
} 
} 

JedisSentinelSlavePool.java

代码语言:javascript复制
package com.ding.utils; 
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import redis.clients.jedis.HostAndPort; 
import redis.clients.jedis.Jedis; 
import redis.clients.jedis.JedisPubSub; 
import redis.clients.jedis.Protocol; 
import redis.clients.jedis.exceptions.JedisConnectionException; 
import redis.clients.jedis.exceptions.JedisException; 
import redis.clients.util.Pool; 
import java.security.InvalidParameterException; 
import java.util.Arrays; 
import java.util.HashSet; 
import java.util.List; 
import java.util.Set; 
import java.util.concurrent.atomic.AtomicBoolean; 
public class JedisSentinelSlavePool extends Pool<Jedis> { 
private final String masterName; 
protected GenericObjectPoolConfig poolConfig; 
protected int connectionTimeout = Protocol.DEFAULT_TIMEOUT; 
protected int soTimeout = Protocol.DEFAULT_TIMEOUT; 
protected String password; 
protected int database = Protocol.DEFAULT_DATABASE; 
protected String clientName; 
protected final Set<JedisSentinelSlavePool.MasterListener> masterListeners = new HashSet<JedisSentinelSlavePool.MasterListener>(); 
protected Logger logger = LoggerFactory.getLogger(JedisSentinelSlavePool.class.getName()); 
private volatile JedisSentinelSlaveFactory factory; 
private volatile HostAndPort currentSentinel; 
private Set<String> sentinels; 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig) { 
this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, null, 
Protocol.DEFAULT_DATABASE); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels) { 
this(masterName, sentinels, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, null, 
Protocol.DEFAULT_DATABASE); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, String password) { 
this(masterName, sentinels, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, password); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, int timeout, final String password) { 
this(masterName, sentinels, poolConfig, timeout, password, Protocol.DEFAULT_DATABASE); 
System.out.println("======================================="); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, final int timeout) { 
this(masterName, sentinels, poolConfig, timeout, null, Protocol.DEFAULT_DATABASE); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, final String password) { 
this(masterName, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, password); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, int timeout, final String password, 
final int database) { 
this(masterName, sentinels, poolConfig, timeout, timeout, password, database); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, int timeout, final String password, 
final int database, final String clientName) { 
this(masterName, sentinels, poolConfig, timeout, timeout, password, database, clientName); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, final int timeout, final int soTimeout, 
final String password, final int database) { 
this(masterName, sentinels, poolConfig, timeout, soTimeout, password, database, null); 
} 
public JedisSentinelSlavePool(String masterName, Set<String> sentinels, 
final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, 
final String password, final int database, final String clientName) { 
this.poolConfig = poolConfig; 
this.connectionTimeout = connectionTimeout; 
this.soTimeout = soTimeout; 
this.password = password; 
this.database = database; 
this.clientName = clientName; 
this.masterName = masterName; 
this.sentinels = sentinels; 
HostAndPort aSentinel = initsentinels(this.sentinels, masterName); 
initPool(aSentinel); 
} 
public void destroy() { 
for (JedisSentinelSlavePool.MasterListener m : masterListeners) { 
m.shutdown(); 
} 
super.destroy(); 
} 
public HostAndPort getCurrentSentinel() { 
return currentSentinel; 
} 
private void initPool(HostAndPort sentinel) { 
System.out.println("123123113312"); 
if (!sentinel.equals(currentSentinel)) { 
currentSentinel = sentinel; 
if (factory == null) { 
factory = new JedisSentinelSlaveFactory(sentinel.getHost(), sentinel.getPort(), connectionTimeout, 
soTimeout, password, database, clientName, false, null, null, null,masterName); 
initPool(poolConfig, factory); 
} else { 
factory.setHostAndPortOfASentinel(currentSentinel); 
// although we clear the pool, we still have to check the 
// returned object 
// in getResource, this call only clears idle instances, not 
// borrowed instances 
internalPool.clear(); 
} 
logger.info("Created JedisPool to sentinel at "   sentinel); 
} 
} 
private HostAndPort initsentinels(Set<String> sentinels, final String masterName) { 
HostAndPort aSentinel = null; 
boolean sentinelAvailable = false; 
logger.info("Trying to find a valid sentinel from available Sentinels..."); 
for (String sentinelStr : sentinels) { 
final HostAndPort hap = HostAndPort.parseString(sentinelStr); 
logger.info("Connecting to Sentinel "   hap); 
Jedis jedis = null; 
try { 
jedis = new Jedis(hap.getHost(), hap.getPort()); 
sentinelAvailable = true; 
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); 
if (masterAddr == null || masterAddr.size() != 2) { 
logger.warn("Can not get master addr from sentinel, master name: "   masterName 
  ". Sentinel: "   hap   "."); 
continue; 
} 
aSentinel = hap; 
logger.info("Found a Redis Sentinel at "   aSentinel); 
break; 
} catch (JedisException e) { 
logger.warn("Cannot get master address from sentinel running @ "   hap   ". Reason: "   e 
  ". Trying next one."); 
} finally { 
if (jedis != null) { 
jedis.close(); 
} 
} 
} 
if (aSentinel == null) { 
if (sentinelAvailable) { 
// can connect to sentinel, but master name seems to not monitored 
throw new JedisException("Can connect to sentinel, but "   masterName 
  " seems to be not monitored..."); 
} else { 
throw new JedisConnectionException("All sentinels down, cannot determine where is " 
  masterName   " master is running..."); 
} 
} 
logger.info("Found Redis sentinel running at "   aSentinel   ", starting Sentinel listeners..."); 
for (String sentinel : sentinels) { 
final HostAndPort hap = HostAndPort.parseString(sentinel); 
JedisSentinelSlavePool.MasterListener masterListener = new JedisSentinelSlavePool.MasterListener(masterName, hap.getHost(), hap.getPort()); 
// whether MasterListener threads are alive or not, process can be stopped 
masterListener.setDaemon(true); 
masterListeners.add(masterListener); 
masterListener.start(); 
} 
return aSentinel; 
} 
/** 
* @deprecated starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be 
* done using @see {@link redis.clients.jedis.Jedis#close()} 
*/ 
@Override 
@Deprecated 
public void returnBrokenResource(final Jedis resource) { 
if (resource != null) { 
returnBrokenResourceObject(resource); 
} 
} 
/** 
* @deprecated starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be 
* done using @see {@link redis.clients.jedis.Jedis#close()} 
*/ 
@Override 
@Deprecated 
public void returnResource(final Jedis resource) { 
if (resource != null) { 
resource.resetState(); 
returnResourceObject(resource); 
} 
} 
private HostAndPort toHostAndPort(List<String> getMasterAddrByNameResult) { 
String host = getMasterAddrByNameResult.get(0); 
int port = Integer.parseInt(getMasterAddrByNameResult.get(1)); 
return new HostAndPort(host, port); 
} 
protected class MasterListener extends Thread { 
protected String masterName; 
protected String host; 
protected int port; 
protected long subscribeRetryWaitTimeMillis = 5000; 
protected volatile Jedis j; 
protected AtomicBoolean running = new AtomicBoolean(false); 
protected MasterListener() { 
} 
public MasterListener(String masterName, String host, int port) { 
super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port)); 
this.masterName = masterName; 
this.host = host; 
this.port = port; 
} 
public MasterListener(String masterName, String host, int port, 
long subscribeRetryWaitTimeMillis) { 
this(masterName, host, port); 
this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis; 
} 
@Override 
public void run() { 
running.set(true); 
while (running.get()) { 
j = new Jedis(host, port); 
try { 
// double check that it is not being shutdown 
if (!running.get()) { 
break; 
} 
j.subscribe(new SentinelSlaveChangePubSub(), " switch-master"," slave"," sdown"," odown"," reboot"); 
} catch (JedisConnectionException e) { 
if (running.get()) { 
logger.error("Lost connection to Sentinel at "   host   ":"   port 
  ". Sleeping 5000ms and retrying.", e); 
try { 
Thread.sleep(subscribeRetryWaitTimeMillis); 
} catch (InterruptedException e1) { 
logger.info( "Sleep interrupted: ", e1); 
} 
} else { 
logger.info("Unsubscribing from Sentinel at "   host   ":"   port); 
} 
} finally { 
j.close(); 
} 
} 
} 
public void shutdown() { 
try { 
logger.info("Shutting down listener on "   host   ":"   port); 
running.set(false); 
// This isn't good, the Jedis object is not thread safe 
if (j != null) { 
j.disconnect(); 
} 
} catch (Exception e) { 
logger.error("Caught exception while shutting down: ", e); 
} 
} 
private class SentinelSlaveChangePubSub extends JedisPubSub { 
@Override 
public void onMessage(String channel, String message) { 
if(masterName==null) { 
logger.error("Master Name is null!"); 
throw new InvalidParameterException("Master Name is null!"); 
} 
logger.info("Get message on chanel: "   channel   " published: "   message   "."   " current sentinel "   host   ":"   port ); 
String[] msg = message.split(" "); 
List<String> msgList = Arrays.asList(msg); 
if(msgList.isEmpty()) {return;} 
boolean needResetPool = false; 
if( masterName.equalsIgnoreCase(msgList.get(0))) { //message from channel  switch-master 
//message looks like [ switch-master mymaster 192.168.0.2 6479 192.168.0.1 6479] 
needResetPool = true; 
} 
int tmpIndex = msgList.indexOf("@")   1; 
//message looks like [ reboot slave 192.168.0.3:6479 192.168.0.3 6479 @ mymaster 192.168.0.1 6479] 
if(tmpIndex >0 && masterName.equalsIgnoreCase(msgList.get(tmpIndex)) ) { //message from other channels 
needResetPool = true; 
} 
if(needResetPool) { 
HostAndPort aSentinel = initsentinels(sentinels, masterName); 
initPool(aSentinel); 
} else { 
logger.info("message is not for master "   masterName); 
} 
} 
} 
} 
} 

JedisSentinelSlaveFactory.java

代码语言:javascript复制
package com.ding.utils; 
import org.apache.commons.pool2.PooledObject; 
import org.apache.commons.pool2.PooledObjectFactory; 
import org.apache.commons.pool2.impl.DefaultPooledObject; 
import redis.clients.jedis.BinaryJedis; 
import redis.clients.jedis.HostAndPort; 
import redis.clients.jedis.Jedis; 
import redis.clients.jedis.exceptions.InvalidURIException; 
import redis.clients.jedis.exceptions.JedisException; 
import redis.clients.util.JedisURIHelper; 
import javax.net.ssl.HostnameVerifier; 
import javax.net.ssl.SSLParameters; 
import javax.net.ssl.SSLSocketFactory; 
import java.net.URI; 
import java.security.SecureRandom; 
import java.util.List; 
import java.util.Map; 
import java.util.concurrent.atomic.AtomicReference; 
public class JedisSentinelSlaveFactory implements PooledObjectFactory<Jedis> { 
private final String masterName; 
private final int retryTimeWhenRetrieveSlave = 5; 
private final AtomicReference<HostAndPort> hostAndPortOfASentinel = new AtomicReference<HostAndPort>(); 
private final int connectionTimeout; 
private final int soTimeout; 
private final String password; 
private final int database; 
private final String clientName; 
private final boolean ssl; 
private final SSLSocketFactory sslSocketFactory; 
private SSLParameters sslParameters; 
private HostnameVerifier hostnameVerifier; 
public JedisSentinelSlaveFactory(final String host, final int port, final int connectionTimeout, 
final int soTimeout, final String password, final int database, final String clientName, 
final boolean ssl, final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters, 
final HostnameVerifier hostnameVerifier,String masterName) { 
this.hostAndPortOfASentinel.set(new HostAndPort(host, port)); 
this.connectionTimeout = connectionTimeout; 
this.soTimeout = soTimeout; 
this.password = password; 
this.database = database; 
this.clientName = clientName; 
this.ssl = ssl; 
this.sslSocketFactory = sslSocketFactory; 
this.sslParameters = sslParameters; 
this.hostnameVerifier = hostnameVerifier; 
this.masterName = masterName; 
} 
public JedisSentinelSlaveFactory(final URI uri, final int connectionTimeout, final int soTimeout, 
final String clientName, final boolean ssl, final SSLSocketFactory sslSocketFactory, 
final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier,String masterName) { 
if (!JedisURIHelper.isValid(uri)) { 
throw new InvalidURIException(String.format( 
"Cannot open Redis connection due invalid URI. %s", uri.toString())); 
} 
this.hostAndPortOfASentinel.set(new HostAndPort(uri.getHost(), uri.getPort())); 
this.connectionTimeout = connectionTimeout; 
this.soTimeout = soTimeout; 
this.password = JedisURIHelper.getPassword(uri); 
this.database = JedisURIHelper.getDBIndex(uri); 
this.clientName = clientName; 
this.ssl = ssl; 
this.sslSocketFactory = sslSocketFactory; 
this.sslParameters = sslParameters; 
this.hostnameVerifier = hostnameVerifier; 
this.masterName = masterName; 
} 
public void setHostAndPortOfASentinel(final HostAndPort hostAndPortOfASentinel) { 
this.hostAndPortOfASentinel.set(hostAndPortOfASentinel); 
} 
@Override 
public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception { 
final BinaryJedis jedis = pooledJedis.getObject(); 
if (jedis.getDB() != database) { 
jedis.select(database); 
} 
} 
@Override 
public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception { 
final BinaryJedis jedis = pooledJedis.getObject(); 
if (jedis.isConnected()) { 
try { 
try { 
jedis.quit(); 
} catch (Exception e) { 
} 
jedis.disconnect(); 
} catch (Exception e) { 
} 
} 
} 
@Override 
public PooledObject<Jedis> makeObject() throws Exception { 
final Jedis jedisSentinel = getASentinel(); 
List<Map<String,String>> slaves = jedisSentinel.sentinelSlaves(this.masterName); 
if(slaves == null || slaves.isEmpty()) { 
throw new JedisException(String.format("No valid slave for master: %s",this.masterName)); 
} 
DefaultPooledObject<Jedis> result = tryToGetSlave(slaves); 
if(null != result) { 
return result; 
} else { 
throw new JedisException(String.format("No valid slave for master: %s, after try %d times.", 
this.masterName,retryTimeWhenRetrieveSlave)); 
} 
} 
private DefaultPooledObject<Jedis> tryToGetSlave(List<Map<String,String>> slaves) { 
SecureRandom sr = new SecureRandom(); 
int retry = retryTimeWhenRetrieveSlave; 
while(retry >= 0) { 
retry--; 
int randomIndex = sr.nextInt(slaves.size()); 
String host = slaves.get(randomIndex).get("ip"); 
String port = slaves.get(randomIndex).get("port"); 
final Jedis jedisSlave = new Jedis(host,Integer.valueOf(port), connectionTimeout,soTimeout, 
ssl, sslSocketFactory,sslParameters, hostnameVerifier); 
try { 
jedisSlave.connect(); 
if (null != this.password) { 
jedisSlave.auth(this.password); 
} 
if (database != 0) { 
jedisSlave.select(database); 
} 
if (clientName != null) { 
jedisSlave.clientSetname(clientName); 
} 
return new DefaultPooledObject<Jedis>(jedisSlave); 
} catch (Exception e) { 
jedisSlave.close(); 
slaves.remove(randomIndex); 
continue; 
} 
} 
return null; 
} 
private Jedis getASentinel() { 
final HostAndPort hostAndPort = this.hostAndPortOfASentinel.get(); 
final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, 
soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier); 
try { 
jedis.connect(); 
} catch (JedisException je) { 
jedis.close(); 
throw je; 
} 
return jedis; 
} 
@Override 
public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception { 
// TODO maybe should select db 0? Not sure right now. 
} 
@Override 
public boolean validateObject(PooledObject<Jedis> pooledJedis) { 
final BinaryJedis jedis = pooledJedis.getObject(); 
try { 
HostAndPort hostAndPort = this.hostAndPortOfASentinel.get(); 
String connectionHost = jedis.getClient().getHost(); 
int connectionPort = jedis.getClient().getPort(); 
// return hostAndPort.getHost().equals(connectionHost) 
// && hostAndPort.getPort() == connectionPort && jedis.isConnected() 
// && jedis.ping().equals("PONG"); 
return true; 
} catch (final Exception e) { 
return false; 
} 
} 
} 

RDB

快照的运作方式: 当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作: Redis 调用 fork() ,同时拥有父进程和子进程。 子进程将数据集写入到一个临时 RDB 文件中。 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。 这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。 只进行追加操作的文件(append-only file,AOF) 快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。尽管对于某些程序来说, 数据的耐久性并不是最重要的考虑因素, 但是对于那些追求完全耐久能力

RDB优点: (1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备。

(3).RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

RDB缺点 (1)如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。 (2)RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒

AOF

Redis 执行 fork() ,现在同时拥有父进程和子进程。 子进程开始将新 AOF 文件的内容写入到临时文件。对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

AOF的缺点 (1)对于同一份文件AOF文件比RDB数据快照要大。 (2)AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的 (3)数据恢复比较慢,不适合做冷备。

AOF的优点: (1)AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。

0 人点赞