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的从服务。sentinelmaster获取到的master自身信息以及master所有的从信息,将会更新到sentinelsentinelState中及masterssentinelRedisInstance结构)中的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 nodesentinelprotected-mode都是yes,在搭建集群时,若想从远程连接redis集群,需要将redis nodesentinelprotected-mode修改为no,若只修改redis node,从远程连接sentinel后,依然是无法正常使用的,且sentinel的配置文件中没有protected-mode配置项,需要手工添加。

(3)远程访问拒绝

sentinel.conf默认配置

sentinel monitor mymaster 127.0.0.1 6380 1

redissentinel在一台服务器上时,必须指定实际的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整合哨兵

jedisspring整合访问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>
View Code

 

配置整合文件: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);  
                }  
            }  
        });  
    } 
}
View Code

在程序中注入该伪service访问redis

 

 

 

 

posted on 2018-06-06 10:46  javaGreenHand。。。  阅读(70)  评论(0)    收藏  举报