本文正在参加「金石方案」

作者:京东零售 王雷

1、Redis集群方案比较

• 岗兵形式

Redis缓存高可用集群

在redis3.0曾经的版别要完成集群一般是凭借岗兵sentinel东西来监控master节点的状况,假如master节点反常,则会做主从切换,将某一台slave作为master,岗兵的装备略微杂乱,而且功用和高可用性等各方面体现一般。

特别是在主从切换的瞬间存在拜访瞬断的状况,而且岗兵形式只要一个主节点对外供给服务,没法支持很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据康复或主从同步的效率。

• 高可用集群形式

Redis缓存高可用集群

redis集群是一个由多个主从节点群组成的分布式服务器群,它具有仿制、高可用和分片特性。Redis集群不需求sentinel岗兵也能完成节点移除和毛病搬运的功用。

需求将每个节点设置成集群形式,这种集群形式没有中心节点,可水平扩展,据官方文档称能够线性扩展到上万个节点(官方引荐不超越1000个节点)。redis集群的功用和高可用性均优于之前版别的岗兵形式,且集群装备非常简单。

2、Redis高可用集群建立

• redis集群建立

redis集群需求至少三个master节点,咱们这儿建立三个master节点,而且给每个master再建立一个slave节点,一共6个redis节点,这儿用三台机器布置6个redis实例,每台机器一主一从,建立集群的过程如下:


第一步:在第一台机器的/usr/local下创立文件夹redis-cluster,然后在其下面分别创立2个文件夾如下
(1)mkdir -p /usr/local/redis-cluster
(2)mkdir 8001 8004
第二步:把之前的redis.conf装备文件copy到8001下,修正如下内容:
(1)daemonize yes
(2)port 8001(分别对每个机器的端口号进行设置)
(3)pidfile /var/run/redis_8001.pid  # 把pid进程号写入pidfile装备的文件
(4)dir /usr/local/redis-cluster/8001/(指定数据文件存放方位,必须要指定不同的目录方位,否则会丢掉数据)
(5)cluster-enabled yes(发动集群形式)
(6)cluster-config-file nodes-8001.conf(集群节点信息文件,这儿800x最好和port对应上)
(7)cluster-node-timeout 10000
 (8)# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,假如有多块网卡能够配多个ip,代表答应客户端经过机器的哪些网卡ip去拜访,内网一般能够不装备bind,注释掉即可)
 (9)protected-mode  no   (关闭保护形式)
 (10)appendonly yes
假如要设置暗码需求增加如下装备:
 (11)requirepass test     (设置redis拜访暗码)
 (12)masterauth test      (设置集群节点间拜访暗码,跟上面一致)
第三步:把修正后的装备文件,copy到8004,修正第2、3、4、6项里的端口号,能够用批量替换:
:%s/源字符串/目的字符串/g 
第四步:另外两台机器也需求做上面几步操作,第二台机器用8002和8005,第三台机器用8003和8006
第五步:分别发动6个redis实例,然后检查是否发动成功
(1)/usr/local/redis-5.0.3/src/redis-server /usr/local/redis-cluster/800*/redis.conf
(2)ps -ef | grep redis 查看是否发动成功
第六步:用redis-cli创立整个redis集群(redis5曾经的版别集群是依托ruby脚本redis-trib.rb完成)
# 下面指令里的1代表为每个创立的主服务器节点创立一个从服务器节点
# 执行这条指令需求确认三台机器之间的redis实例要能彼此拜访,能够先简单把一切机器防火墙关掉,假如不关闭防火墙则需求打开redis服务端口和集群节点gossip通讯端口16379(默许是在redis端口号上加1W)
# 关闭防火墙
# systemctl stop firewalld # 临时关闭防火墙
# systemctl disable firewalld # 制止开机发动
# 留意:下面这条创立集群的指令咱们不要直接仿制,里边的空格编码或许有问题导致创立集群不成功
(1)/usr/local/redis-5.0.3/src/redis-cli -a test --cluster create --cluster-replicas 1 192.168.0.61:8001 192.168.0.62:8002 192.168.0.63:8003 192.168.0.61:8004 192.168.0.62:8005 192.168.0.63:8006 
第七步:验证集群:
(1)衔接任意一个客户端即可:./redis-cli -c -h -p (-a拜访服务端暗码,-c表明集群形式,指定ip地址和端口号)
    如:/usr/local/redis-5.0.3/src/redis-cli -a test -c -h 192.168.0.61 -p 800*
(2)进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表)
(3)进行数据操作验证
(4)关闭集群则需求逐个进行关闭,运用指令:
/usr/local/redis-5.0.3/src/redis-cli -a test -c -h 192.168.0.60 -p 800* shutdown

3、Java操作redis集群

凭借redis的java客户端jedis能够操作以上集群,引证jedis版别的maven坐标如下:


 <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

Java编写拜访redis集群的代码非常简单,如下所示:


     public class JedisClusterTest {
    public static void main(String[] args) throws IOException {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxIdle(10);
        config.setMinIdle(5);
        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("192.168.0.61", 8001));
        jedisClusterNode.add(new HostAndPort("192.168.0.62", 8002));
        jedisClusterNode.add(new HostAndPort("192.168.0.63", 8003));
        jedisClusterNode.add(new HostAndPort("192.168.0.61", 8004));
        jedisClusterNode.add(new HostAndPort("192.168.0.62", 8005));
        jedisClusterNode.add(new HostAndPort("192.168.0.63", 8006));
        JedisCluster jedisCluster = null;
        try {
            //connectionTimeout:指的是衔接一个url的衔接等待时间
            //soTimeout:指的是衔接上一个url,获取response的回来等待时间
            jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "zhuge", config);
            System.out.println(jedisCluster.set("cluster", "test"));
            System.out.println(jedisCluster.get("cluster"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedisCluster != null)
                jedisCluster.close();
        }
    }
}
运转作用如下:
OK
test

集群的Spring Boot整合Redis衔接代码见示例项目:redis-sentinel-cluster

1、引入相关依靠:


<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
</dependency>

springboot项目核心装备:


server:
  port: 8080
spring:
  redis:
    database: 0
    timeout: 3000
    password: wl
    cluster:
      nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006
   lettuce:
      pool:
        max-idle: 50
        min-idle: 10
        max-active: 100
        max-wait: 1000  

拜访代码:

@RestController
public class IndexController {
    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/test_cluster")
    public void testCluster() throws InterruptedException {
       stringRedisTemplate.opsForValue().set("test", "666");
       System.out.println(stringRedisTemplate.opsForValue().get("test"));
    }
}

4、Redis集群原理剖析

Redis Cluster 将一切数据划分为 16384 个 slots(槽位),每个节点担任其间一部分槽位。槽位的信息存储于每个节点中。

当 Redis Cluster 的客户端来衔接集群时,它也会得到一份集群的槽位装备信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,能够直接定位到方针节点。一起由于槽位的信息或许会存在客户端与服务器不一致的状况,还需求纠正机制来完成槽位信息的校验调整。

• 槽位定位算法

Cluster 默许会对 key 值运用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到详细槽位。

HASH_SLOT = CRC16(key) mod 16384

• 跳转重定位

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 地点的槽位并不归自己管理,这时它会向客户端发送一个特别的跳转指令携带方针操作的节点地址,告知客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续一切 key 将运用新的槽位映射表。

Redis缓存高可用集群

• Redis集群节点间的通讯机制

redis cluster节点间采取gossip协议进行通讯

维护集群的元数据(集群节点信息,主从人物,节点数量,各节点共享的数据等)有两种方式:会集式和gossip

• 会集式

长处在于元数据的更新和读取,时效性非常好,一旦元数据呈现改变当即就会更新到会集式的存储中,其他节点读取的时候当即就能够当即感知到;缺乏在于一切的元数据的更新压力全部会集在一个地方,或许导致元数据的存储压力。 很多中间件都会凭借zookeeper会集式存储元数据。

• gossip:

Redis缓存高可用集群

gossip协议包括多种音讯,包括ping,pong,meet,fail等等。

1)meet:某个节点发送meet给新参加的节点,让新节点参加集群中,然后新节点就会开始与其他节点进行通讯;

2)ping:每个节点都会频繁给其他节点发送ping,其间包括自己的状况还有自己维护的集群元数据,相互经过ping交流元数据(相似自己感知到的集群节点增加和移除,hash slot信息等);

3)pong: 对ping和meet音讯的回来,包括自己的状况和其他信息,也能够用于信息播送和更新;

4)fail: 某个节点判别另一个节点fail之后,就发送fail给其他节点,告诉其他节点,指定的节点宕机了。

gossip协议的长处在于元数据的更新比较涣散,不是会集在一个地方,更新请求会陆陆续续,打到一切节点上去更新,有必定的延时,降低了压力;缺陷在于元数据更新有延时或许导致集群的一些操作会有一些滞后。

gossip通讯的10000端口

每个节点都有一个专门用于节点间gossip通讯的端口,便是自己供给服务的端口号+10000,比方7001,那么用于节点间通讯的便是17001端口。 每个节点每隔一段时间都会往另外几个节点发送ping音讯,一起其他几点接收到ping音讯之后回来pong音讯。

• 网络颤动

实在世界的机房网络往往并不是风平浪静的,它们经常会产生各式各样的小问题。比方网络颤动便是非常常见的一种现象,突然之间部分衔接变得不行拜访,然后很快又康复正常。

为处理这种问题,Redis Cluster 供给了一种选项cluster-node-timeout,表明当某个节点继续 timeout 的时间失联时,才干够确定该节点呈现毛病,需求进行主从切换。假如没有这个选项,网络颤动会导致主从频繁切换 (数据的从头仿制)。

Redis集群推举原理剖析

当slave发现自己的master变为FAIL状况时,便测验进行Failover,以期成为新的master。由于挂掉的master或许会有多个slave,然后存在多个slave竞赛成为master节点的过程, 其过程如下:

1)slave发现自己的master变为FAIL

2)将自己记载的集群currentEpoch加1,并播送FAILOVER_AUTH_REQUEST 信息

3)其他节点收到该信息,只要master响应,判别请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

4)测验failover的slave搜集master回来的FAILOVER_AUTH_ACK

5)slave收到超越对折master的ack后变成新Master(这儿解释了集群为什么至少需求三个主节点,假如只要两个,当其间一个挂了,只剩一个主节点是不能推举成功的)

6)slave播送Pong音讯告诉其他集群节点。

从节点并不是在主节点一进入 FAIL 状况就马上测验建议推举,而是有必定推迟,必定的推迟确保咱们等待FAIL状况在集群中传达,slave假如当即测验推举,其它masters或许尚未意识到FAIL状况,或许会拒绝投票

推迟计算公式:

DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

SLAVE_RANK表明此slave已经从master仿制数据的总量的rank。Rank越小代表已仿制的数据越新。这种方式下,持有最新数据的slave将会首先建议推举(理论上)。

集群脑裂数据丢掉问题

redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外供给写服务,一旦网络分区康复,会将其间一个主节点变为从节点,这时会有很多数据丢掉。

躲避方法能够在redis装备里加上参数(这种方法不或许百分百防止数据丢掉,参阅集群leader推举机制):

 min-replicas-to-write 1  //写数据成功最少同步的slave数量,这个数量能够仿照大于对折机制装备,比方集群一共三个节点能够装备1,加上leader便是2,超越了对折

*留意:这个装备在必定程度上会影响集群的可用性,比方slave要是少于1个,这个集群就算leader正常也不能供给服务了,需求详细场景权衡挑选。

集群是否完好才干对外供给服务

当redis.conf的装备
cluster-require-full-coverage为no时,表明当担任一个插槽的主库下线且没有相应的从库进行毛病康复时,集群仍然可用,假如为yes则集群不行用。

Redis集群为什么至少需求三个master节点,而且引荐节点数为奇数?

由于新master的推举需求大于对折的集群master节点同意才干推举成功,假如只要两个master节点,当其间一个挂了,是达不到推举新master的条件的。

奇数个master节点能够在满意推举该条件的基础上节省一个节点,比方三个master节点和四个master节点的集群相比,咱们假如都挂了一个master节点都能推举新master节点,假如都挂了两个master节点都没法推举新master节点了,所以奇数的master节点更多的是从节省机器资源视点动身说的。

Redis集群对批量操作指令的支持

关于相似mset,mget这样的多个key的原生批量操作指令,redis集群只支持一切key落在同一slot的状况,假如有多个key必定要用mset指令在redis集群上操作,则能够在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:

 mset {user1}:1:name zhuge {user1}:1:age 18

假定name和age计算的hash slot值不一样,可是这条指令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。

岗兵leader推举流程

当一个master服务器被某sentinel视为下线状况后,该sentinel会与其他sentinel协商选出sentinel的leader进行毛病搬运工作。每个发现master服务器进入下线的sentinel都能够要求其他sentinel选自己为sentinel的leader,推举是先到先得。一起每个sentinel每次推举都会自增装备纪元(推举周期),每个纪元中只会挑选一个sentinel的leader。假如一切超越一半的sentinel推举某sentinel作为leader。之后该sentinel进行毛病搬运操作,从存活的slave中推举出新的master,这个推举过程跟集群的master推举很相似。

岗兵集群只要一个岗兵节点,redis的主从也能正常运转以及推举master,假如master挂了,那唯一的那个岗兵节点便是岗兵leader了,能够正常推举新master。

不过为了高可用一般都引荐至少布置三个岗兵节点。为什么引荐奇数个岗兵节点原理跟集群奇数个master节点相似。