redis哨兵
redis的高级模式分为哨兵和集群两种,两者都是基于redis的主从复制完成的
1、redis的主从复制
cp redis.conf redis-master.conf #主节点配置 redis-server redis-master.conf #启动主 cp redis.conf redis-slave01.conf #从1配置,修改端口 6380 redis-server redis-slave01.conf #启动从1 redis-cli –p 6380 #登录从1 slaveof 127.0.0.1 6379 #挂接到主 cp redis.conf redis-slave02.conf #从2配置,修改端口6381 redis-server redis-slave02.conf #启动从2 redis-cli –p 6381 #登录从2 slaveof 127.0.0.1 6379 #挂接到主
查看状态
检查配置 info #查看所有信息 info Replication #只查看Replication片段信息
#查看结果,确认状态 127.0.0.1:6379> info Replication # Replication role:master #说明master生效 connected_slaves:2 #master主节点下有两个从节点 slave0:ip=127.0.0.1,port=6380,state=online,offset=845,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=845,lag=1 master_repl_offset:845 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:844 #测试主从复制 127.0.0.1:6379>set name tony 127.0.0.1:6380>get name #读取到同步的信息 127.0.0.1:6381>get name
#尝试往从上写数据,报错,redis的主从复制不允许往从上写数据 127.0.0.1:6381>set name hellen (error) READONLY You can't write against a read only slave.
2、哨兵模式
Sentinel系统用于管理多个Redis服务器(instance),该系统执行以下三个任务:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification):当被监控的某个 Redis 服务器出现问题时,Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。
工作原理:sentinel以每10秒一次的频率向master发送info命令,通过info的回复来分析master信息,master的回复主要包含了两部分信息,一部分是master自身的信息,一部分是master所有的slave(从)的信息,所以sentinel可以自动发现master的从服务。sentinel从master获取到的master自身信息以及master所有的从信息,将会更新到sentinel的sentinelState中及masters(sentinelRedisInstance结构)中的slaves字典中。
一个哨兵也可能宕机,一次启动两个哨兵,自身也实现"高可用
cp sentinel.conf sentinel2.conf #复制哨兵 26379为26380 #修改端口 sentinel monitor mymaster192.168.163.20 6379 2 #修改sentinel.conf配置文件 sentinel #关键字 mymaster #自定义名称,代码中要写这个; 192.168.163.20 #这个哨兵一开始监控的主机, 63791 #默认配置的主从redis中的主的端口 2 代表有几个哨兵宣布某个dode死亡时(心跳info,根据配置的判断死亡时间),才触发(按优先级,优先级越小的几率越高,优先级相同先挂接的优先)选举工作;主节点启动好后会自动变成从节点;配置文件中的这句话会被自动修改;住的redis.conf文件也会自动被修改(主变成从);
打开端口或防火墙
6379,6380,6381,26379,26380
service iptables stop
redis-sentinel sentinel.conf #启动哨兵 redis-sentinel sentinel2.conf #启动哨兵2,形成哨兵的高可用
测试sentinel
ps –ef|grep redis #查看进程 6010 5709 0 00:38 pts/2 00:00:13 redis-server 127.0.0.1:6379 6012 5710 0 00:38 pts/2 00:00:13 redis-server 127.0.0.1:6380 6014 5721 0 00:38 pts/3 00:00:13 redis-server 127.0.0.1:6381 6018 5927 1 00:39 pts/6 00:00:21 redis-server *:26379 [sentinel] 6047 6037 1 00:41 pts/7 00:00:19 redis-server *:26380 [sentinel] 6241 2512 0 01:10 pts/1 00:00:00 grep redis 测试 kill 6010 #可以看到slave的两个节点开始报链接拒绝错误,不一会哨兵将6380切换为主 info #在6380上info命令,可以看到其role:master kill 6018 #删除一个哨兵,在6380上set数据,6381自动同步
sentineld的坑:
(1)错误切换后更改配置文件,在重启前要把日志文件删除;还要把redis中的配置文件删除sentinel自动增加的内容;
(2)默认情况下,Redis node和sentinel的protected-mode都是yes,在搭建集群时,若想从远程连接redis集群,需要将redis node和sentinel的protected-mode修改为no,若只修改redis node,从远程连接sentinel后,依然是无法正常使用的,且sentinel的配置文件中没有protected-mode配置项,需要手工添加。
(3)远程访问拒绝
sentinel.conf默认配置
sentinel monitor mymaster 127.0.0.1 6380 1
当redis和sentinel在一台服务器上时,必须指定实际的IP地址
sentinel monitor mymaster 192.168.163.30 6380 1
选举机制
sentinel.conf中96行左右的位置 sentinel monitor mymaster 127.0.0.1 6379 2 最后的2代表选举的个数,这个值非常关键。其中的2表示只有在两个sential进程发现master不可用时才执行failover故障转移。例如:即使一个master宕机,如果投票个数未超过1个,redis不会触发failover,不会触发选举,而是一直等待master恢复,当master恢复,一切又工作正常。只有当投票数大于等于1时,才认为master才会触发选举,自动从众多的slave中选择一个节点升级为master,其他自动从节点自动连接次节点。同时会自动修改sentinel.conf文件 sentinel monitor mymaster 192.168.163.30 6380 1 默认30秒进行切换
6.3.5修改从节点的选举优先级 redis.conf slave-priority 100 这样当Master挂掉的时候Sentinel会优先选择slave-priority值较小的作为新的Master。
6.3.6sentinel.conf配置详解 配置语句 说明 daemonize yes 以后台进程模式运行 port 26379 哨兵的端口号,该端口号默认为26379,不得与任何redis node的端口号重复 logfile "/var/log/redis/sentinel.log" log文件所在地 sentinel monitor master1 192.168.163.306379 1 (第一次配置时)哨兵对哪个master进行监测,此处的master1为一“别名”可以任意,将来程序访问时使用,如sentinel-26379。然后哨兵会通过这个别名后的IP知道整个该master内的slave关系。因此你不用在此配置slave是什么而由哨兵自己去维护这个“链表”。 sentinel monitor master1 192.168.163.306379 1 最后一个1代表当节点宕机时的触发选举的判断条件,1就代表sentinel认定宕机的个数,必须大于这个个数,选举才开始发生,默认值为2,很坑。 sentinel down-after-milliseconds master1 1000 如果master在多少秒内无反应哨兵会开始进行master-slave间的切换,使用“选举”机制 sentinel failover-timeout master1 5000 如果在多少秒内没有把宕掉的那台master恢复,那哨兵认为这是一次真正的宕机,而排除该宕掉的master作为节点选取时可用的node然后等待一定的设定值的毫秒数后再来探测该节点是否恢复,如果恢复就把它作为一台slave加入哨兵监测节点群并在下一次切换时为他分配一个“选取号”。
3、spring整合哨兵
jedis和spring整合访问sentinel需要一个整合包,这个整合包是通过spring-data支持。整合后会创建RedisTemplate对象,在伪service中就可以调用。
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>
配置整合文件: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: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"> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxTotal}" /> <property name="minIdle" value="${redis.minIdle}" /> <property name="maxIdle" value="${redis.maxIdle}" /> </bean> <bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> <!--注入主redis的名字--> <property name="master"> <bean class="org.springframework.data.redis.connection.RedisNode"> <property name="name" value="${redis.sentinel.masterName}"></property> </bean> </property> <!--定义哨兵群--> <property name="sentinels"> <set> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel1.port}" type="int"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel2.port}" type="int"></constructor-arg> </bean> </set> </property> </bean> <!-- p:password="${redis.sentinel.password}" --> <!--定义连接创建工厂--> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <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>
配置文件:redis-sentinel.properties(注意一个坑,属性文件中不能有空格,redis源码中不会去过滤空格,导致如果有空格就无法连接错误Can connect to sentinel, but mymaster seems to be not monitored。)
redis.minIdle=300 redis.maxIdle=500 redis.maxTotal=5000 redis.sentinel1.host=192.168.163.200 redis.sentinel1.port=26379 redis.sentinel2.host=192.168.163.200 redis.sentinel2.port=26380 redis.sentinel.masterName=mymaster redis.sentinel.password=123456
在java代码中可以获取:RedisTemplate
@Autowired(required = false) #有就注入,没有就不必让spring注入 RedisTemplate<?, ?> redisTemplate;
利用RedisTemplate封装一个操作哨兵的伪service
package com.jt.common.service; 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 public class RedisSentinelService { private Logger logger = LoggerFactory.getLogger("RedisSentinelService"); //有的工程需要,有的工程不需要。设置required=false,有就注入,没有就不注入。 @Autowired(required = false) 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)); return null; } }); } //设置值后并设置过期时间 public void set(final String key, final String value, final long seconds) { redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.set( redisTemplate.getStringSerializer().serialize(key), redisTemplate.getStringSerializer().serialize(value)); connection.expire(redisTemplate.getStringSerializer().serialize(key), seconds); 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); } } }); } }
在程序中注入该伪service访问redis
posted on 2018-06-06 10:46 javaGreenHand。。。 阅读(70) 评论(0) 收藏 举报
浙公网安备 33010602011771号