【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)

  • Redis Cluster方案

    • 数据分片
    • 哈希后取模
  • 一致性哈希

  • 槽(slot)

    • 如何让相关业务数据强制落在同一个槽
  • 客户端的重定向

  • 重新分片

    • ASK错误
  • ASK错误和MOVED错误

  • Redis Group

    • 故障检测
  • 故障转移

  • 选举新的master节点

  • 为什么槽定义为16384个

  • 手动配置一个Redis Cluster集群

    • 为什么至少需要3个maser节点
  • 手把手搭建一个3主3从Redi集群

  • 搭建集群常见错误

  • Redis Cluster集群常用命令

  • 客户端如何使用Redis Cluster集群

  • Redis Cluster的不足

  • 总结

[](()前言

===============================================================

Redis作为一款优秀的Nosql数据库,使用非常广泛,但是在有些场景下,一台Redis服务器是不能满足要求的,我们可能需要多台Redis服务器来一起工作,而且如果仅仅使用一台Redis服务器,那么假如这一台服务器挂了,也会给业务带来很大的影响,严重的可能会导致整个系统不可用,所以一个高可用的分布式Redis集群是非常必要的。

[](()Redis集群服务

======================================================================

Redis当中的集群方案实现方式可以分为三大类:主从复制集群,基于哨兵机制实现的高可用集群和Redis分布式Cluster方案,下面就让我们分别来进行介绍

[](()主从复制


主动复制,即:master-slave方案,是一个非常常见的设计模型。其中主库用来读写,并且将数据同步给从库,一旦主库挂了,那么从库就可以升级为主库。

[](()配置一主两从master-slave集群

配置文件中配置replicaof no one则表示当前Redis服务器为主服务器,然后从服务器使用配置replicaof host port这样就成为了master的从库。配置成功之后,连接上主从库,可以执行命令replication info命令进行查看信息。

  • 主库执行info replication命令显示信息如下(role表示当前是一个master库,下面的slaveX展示的就是从库的信息):

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第1张图片

  • 从库执行info replication命令显示信息如下(role表示当前是一个slave库,下面的master_XX展示的就是主库的相关信息):

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第2张图片

配置主从的命令除了可以在配置文件中配置,还可以直接在redis服务器上执行,或者也可以在启动的时候执行./redis-server --slaveof ip port来指定主从服务器。

搭建好主从之后,主服务器上数据就会被同步到从服务器,注意,从服务器默认是只读的,可以通过配置文件replica-read-only no来修改。

题外话:上面的replicaof命令可以替换为slaveof命令,但是建议还是使用replicaof命令,因为slaveof命令在国外被理解奴隶制度,所以当时因为这个命令Redis作者发起过一个投票,半数以上的人支持改名,所以后面Redis就采用了replicaof命令来替换slaveof命令。

[](()主从复制原理分析

master-slave集群的关键在于数据的同步,而在数据同步之前必须先建立连接。

[](()建立连接

建立连接主要分为以下几步:

  • 1、执行slaveof命令时候,从服务器会在本地将主服务器的一些信息(如:IP和端口等信息)保存在redisServer内。

  • 2、创建和主服务器的连接,创建连接之后,从服务器就相当于主服务器的一个客户端。

  • 3、从服务器向主服务发送ping命令,确认连接是否可用,如果Redis服务器需要授权,这一步还会进行授权认证。

  • 4、如果从服务器收到主服务器的pong回复之后,表示当前连接可用,此时从服务器会将自己的服务器的端口号发送给主服务器,主服务器收到之后将其记录在redisClient内。

  • 5、如果从服务器没收到主服务器返回pong,则会发起重连。

PS:建立完连接之后,主从服务器会定时(间隔1s)向对方发送replconf of命令来检测对方是否正常。上图中在主服务器查看从服务器信息中有一个lag属性记录的就是上一次发送心跳包时间。

[](()同步

master和slave服务之间连接建立之后,就会开始进行数据同步,而首次同步一般用于首次建立主从连接的时候,这时候因为是初次建立master-slave关系,所以需要进行数据的全量同步。

首次数据全量同步主要分为以下步骤:

  • 1、从服务器向主服务器发送同步数据命令。

  • 2、主服务器接收到同步数据命令之后,则会执行bgsave命令,在后台生成一个RDB文件,并将其发送给从服务器,此时如果有新的命令过来,主服务器会将其记录在缓冲区内。

  • 3、从服务器收到主服务器发送过来的RDB文件之后,会首先清除自己的数据,然后载入RDB文件来生成数据。

  • 4、当从服务器执行完RDB文件之后,主服务器会将缓冲区的命令发送给从服务器,从服务器再依次执行。

[](()命令传播

执行完首次全量同步之后,这时候主服务器会将自己接收到的改变了数据库状态的命令发送给从服务器,从而使得主从服务器数据始终保持一致。

既然是命令传播,那么就不可避免的会造成数据延迟,Redis当中提供了一个参数来进行优化。

repl-disable-tcp-nodelay no //默认是no

当参数设置为yes时,此时会将数据包进行合并发送,也就是降低了发送频率(发送频率与Linux内核配置有关);当参数设置为no时,则主服务器每执行一个能改变数据库状态的命令就会立刻实时同步给从服务器。

[](()部分重同步

上面就是在服务器正常情况下的同步措施,同步+命令传播可以使得正常情况下主从服务器数据保持一致。然而如果说从服务器因为停电等其他因素导致其和主服务器之间的连接中途断开,那么当连接再次恢复正常之后,如果还是重新全量同步则效率会非常低,也显得没有必要,所以Redis就需要支持部分重同步。

实现部分重同步最关键的地方就是需要记录原先同步的偏移量,只有这样才能在连接恢复正常之后继续实现命令传播,而无需传输整个RDB文件

[](()同步偏移量

主从服务器各自都会维护一个数据复制的偏移量,这个偏移量表示的是发送命令的字节数。

下图就是一个刚建立连接的主从服务器,默认偏移量offset都是0:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第3张图片

当主服务器向从服务器发送100字节之后,主从服务器的偏移量就会变成100。

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第4张图片

那么如果master再次向slave1和slave2传输了一个200字节的命令,slave1接收到了,而slave2没有接收到,那么就会出现以下情况:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第5张图片

这时候当slave2再次和master恢复连接之后,此时slave2服务器会想主服务器发送同步命令,同步命令会带上偏移量,这时候主服务器收到了,发现slave2发送过来额偏移量是100,而自己已经到300了,那么主服务器就会把101到300之间的命令再次进行发送给slave2,从而达到了部分重同步的目的。

[](()复制积压缓冲区

上面的部分重同步貌似看起来能解决问题,但是这又会带来另一个问题,那就是当主服务器将命令发送出去之后,为了实现部分重同步还需要将命令保存起来,否则当从服务器的偏移量低于主服务器时,主服务器也无法将命令重传播。

那么问题就来了,这个命令要保存多久呢?如果一直保存下去就会占据大量的空间,为了解决这个问题,master服务器维护了一个固定长度的FIFO队列,即复制积压缓冲区

当进行命令传播的过程中,master服务器不仅会将命令传播给所有的slave服务器,同时还会将命令写入复制积压缓冲区。复制积压缓冲区默认大小为1MB。

下面就是一个完整的部分重同步流程图:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第6张图片

也就是说,当master服务器记录的偏移量+1已经不存在与复制积压缓冲区了,就会执行一次全量同步,即发送RDB文件给从服务器。

[](()主从服务的不足之处

主从服务器通过读写分离实现了数据的可靠性,但是其并未实现系统的高可用性。其主要存在以下两个问题:

  • 1、首次同步或者部分重同步时需要执行全量同步时发送的RDB文件如果过大,则会非常耗时。

  • 2、假如master服务器挂了,那么系统并不能手动切换master服务器,仍然需要人为进行切换。

[](()哨兵Sentinel机制


Redis的Sentinel机制主要是为了实现Redis服务器的高可用性而设计的一种解决方案。这种方案也是为了弥补主从复制模式的不足,Sentinel机制实现了主从服务的自动切换。

[](()Sentinel原理分析

Sentinel其本身也是一个特殊的Redis服务,在Redis的安装包内,除了redis.conf文件,还有一个sentinel.conf文件,这个就是启动sentinel服务的配置文件,启动命令则通过redis-sentinel来执行(如:./redis-sentinel ../sentinel.conf)或者也可以通过redis-server命令指定参数sentinel来启动(如:./redis-server ../sentinel.conf --sentinel)。

Sentinel主要用来监控Redis集群中的所有节点,当Sentinel服务发现master不可用时,可以实现master服务的自动切换。但是如果Sentinel服务自己挂了怎么办?所以为了实现高可用,Sentinel服务本身也是一个集群,和Redis的master-slave模式不同的是,Sentinel集群之间在正常情况下没有主从关系,相互之间是平等的,只有在需要执行故障转移时才需要进行Leader选举。

下图就是一个3个Sentinel服务集群和1主2从的Redis集群示意图:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第7张图片

Sentinel集群之间的服务会互相监控,然后每个Sentinel服务都会监控所有的master-slave节点,一旦发现master节点不可用,则Sentinel中通过选举产生的Leader节点会执行故障转移,切换master节点。

[](()主观下线和客观下线

Sentinel服务默认以每秒1次的频率向Redis服务节点发送ping命令(Sentinel服务之间也会发送ping命令进行检测)。如果在指定时间内(可以由参数down-after-milliseconds进行控制,默认30s)没有收到有效回复,Sentinel会将该服务器标记为下线,即主观下线

down-after-milliseconds master-name milliseconds

当某一个Sentinel把master服务标记为主观下线之后,会去询问其他Sentinel节点,确认这个master是否真的下线,当达到指定数量的Sentinel服务都认为master服务器已经主观下线,这时候Sentinel就会将master服务标记为客观下线,并执行故障转移操作。

多少个Sentinel服务认定master节点主观下线才会正式将master服务标记为客观下线,由以下参数控制:

sentinel monitor

其中的quorum选项就是决定了这个数量。

需要注意的是,每个Sentinel服务的判断主观下线和客观下线的配置可能不一样,所以当Sentinel1判定master已经主观下线或者客观下线时,其他Sentinel服务并不一定会这么认为,但是只要有一个Sentinel判定master已经客观下线,其就会执行故障转移,但是故障转移并不一定是由判断为客观下线的Sentinel服务来执行,在执行故障转移的之前,Sentinel服务之间必须进行Leader选举。

[](()Leader选举

当某一个或者多个Sentinel服务判定master服务已经下线,其会发起Leader选举,选举出Leader之后,由Leader节点执行故障转移。

[](()Raft选举算法

Sentinel服务的Leader选举是通过Raft算法来实现的。Raft是一个共识算法(consensus algorithm),其核心思想主要有两点:

  • 1、先到先得

  • 2、少数服从多数

在Raft算法中,每个节点都维护了一个属性election timeout,这是一个随机的时间,范围在150ms~300ms之间,哪个节点先到达这个时间,哪个节点就可以发起选举投票。

选举步骤总要可以总结为以下步骤:

  • 1、发起选举的服务首先会给自己投上一票。

  • 2、然后会向其他节点发送投票请求到其他节点,其他节点在收到请求后如果在election timeout范围内还没有投过票,那么就会给发起选举的节点投上一票,然后将election **《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】** timeout重置。

  • 3、如果发起选举的节点获得的票数超过一半,那么当前服务就会成为Leader节点,成为Leader节点之后就会维护一个heartbeat timeout时间属性,在每一次到达heartbeat timeout时间时,Leader节点就会向其他Follow节点发起一个心跳检测。

  • 4、Follow节点收到Leader节点的心跳包之后就会将election timeout清空,这样可以防止Follow节点因为到达election timeout而发起选举。

  • 5、假如Leader节点挂了,那么Follow节点的election timeout将不会被清空,谁先到达,谁就会再次发起选举。

PS:因为election timeout是一个随机值,虽然概率小,但也可能出现两个节点同时发起投票选举,这种情况就可能出现一次选举并不能选出Leader(比如总共4个节点,每个节点都得了2票),此时就会等待下一次首先到达election timeout的节点再次发起投票选举。

如果对Raft算法感兴趣的,可以[点击这里](()观看演示。

[](()Sentinel选举Leader

Sentinel中的选举虽然是源于Raft算法,但是也做了以下改进:

  • 1、触发选举并不是由election timeout时间决定,而是由谁先判定master下线来决定的。

  • 2、Sentinel节点并没有维护election timeout属性,而是维护了一个配置纪元configuration epoch属性,配置纪元是一个计数器(默认0),每一个Sentinel节点的同一个配置纪元只能投票1次(先到先得),每次投票前会将配置纪元自增+1。

  • 3、选举出Leader之后,Leader并不会向Follow节点发送心跳包告诉其他Follow节点自己成为了Leader。

[](()故障转移

当Sentinel选举出Leader之后,Leader就会开始执行故障转移,执行故障转移主要分为一下三步:

  • 1、在已判定客观下线的master服务器的slave服务器中找到一个合格的slave服务器,向其发送replicaof no one命令,使其转换为master服务。

  • 2、向其他从服务器发送replicaof ip port命令,使其成为新master服务的slave节点。

  • 3、将已下线的master服务也设置为新的master服务的slave节点。

[](()如何选举新的master节点

新的master选举条件主要需要参考4个因素:

  • 1、断开连接时长:首先将所有于已下线master节点断开连接时间超过down-after-milliseconds * 10的slave节点删除掉,确保salve节点的数据都是比较新的。

  • 2、slave节点的优先级排序:将所有的salve节点按照优先级进行排序,选出优先级最高的slave节点作为新的master节点(优先级由配置文件参数replica-priority决定,默认100)。

  • 3、复制偏移量:如果有多个优先级相同的slave节点,则选出复制偏移量最大的的slave节点作为新的master节点。

  • 4、进程id:如果还是没选出新的master节点,那么会再次选择进程id最小的slave节点作为新的master节点。

[](()配置Sentinel集群

配置sentinel需要修改sentinel.conf配置文件,主要涉及到以下的一些配置属性:

protected-mode no

port 26380

pidfile /xxx/redis-sentinel-26380.pid

logfile “/xxx/sentinel.log”

dir “/xxx”

sentinel monitor mymaster xx.xxx.xxx.xxx 6370 2 //ip不要设置成127.0.0.1或者localhost

sentinel down-after-milliseconds mymaster 30000

sentinel parallel-syncs mymaster 1

sentinel failover-timeout mymaster 180000

| 参数 | 说明 |

| — | — |

| protected-mode | 是否允许外部网络访问 |

| port | sentinel端口 |

| pidfile | pid文件 |

| logfile | 日志文件 |

| dir | sentinel工作主目录 |

| sentinel monitor | 监控的master服务名称,ip和端口,其中ip建议使用真实ip取代本机ip |

| sentinel down-after-milliseconds | master下线多少毫秒才会被Sentinel 判定为主观下线 |

| sentinel parallel-syncs | 切换新的master-slave时,slave需要从新的master同步数据,这个数字表示允许多少个slave同时复制数据 |

| sentinel failover-timeout | 故障转移超时时间,这个时间有4个含义 |

注意上面的master服务名称可以取值,但是在同一个Sentinel集群中需要保持一致,否则会无法正确监控。

故障转移超时时间用在了以下4个地方:

  • 1、同一个sentinel对同一个master两次failover之间的间隔时间。

  • 2、从检测到master服务器故障开始,到被强制切换到新的master服务器并开始复制数据为止的时间。

  • 3、取消已经在进行故障转移(没有产生任何配置更改的故障转移)所需的时间。

  • 4、将所有salve配置新的master节点所需要的时间。超过这个时间如果仍然没有完成还是会继续进行,但是不一定会按照配置parallel-syncs所指定的并行数来进行。

[](()Sentinel机制日志解析

下图就是一个Sentine故障转移的日志,最开始6371为被Sentinel监控的master服务器:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第8张图片

  • 1-7行表示Sentinel启动完毕,正在监控6371端口的master服务器。

  • 8-9行表示Sentinel发现了master服务有两个slave节点,端口为6370和6372。

  • 10行表示Sentinel判定6371的master服务器已经主观下线。

  • 11行的1/1表示已经满足客观下线条件,所以Sentinel将master判定为客观下线。

  • 12行表示将配置纪元自增1。

  • 13行表示要开始准备故障转移,但是需要先进性Leader选举。

  • 14行表示自己给自己投了1票,因为这里只配置了一个Sentinel,所以他成为了Leader,由它来执行故障转移。

  • 15-17行表示经过一些列条件判定之后,6370被推选为新的master。

  • 18-21行表示Sentinel像6370服务发送slaveof no one命令,使其成为新的master节点,并等待这个过程完成之后修改其配置文件。

  • 22-25行表示6372服务开始同步新的master节点数据。

  • 26行正式将master服务器的地址切换到6370(因为这里只有1主2从,挂了1个还有2个,如果还有其他从服务器,也需要依次来复制数据)。切换master地址之后客户端就可以获取到新的master服务地址。

  • 27行表示将6372服务器添加到新的master服务器的slave列表。

  • 28-29行表示将旧的master-6371服务器设置为新的master服务器的slave节点,这样当6371再次启动之后,会成为新的master服务器的slave节点。

[](()Sentinel机制的使用

Sentinel机制下,客户端应该怎么连接上master服务呢?因为master是可能改变的,所以在Sentinel机制下,客户端需要连接上Sentinel服务,然后从Sentinel服务获得master的地址进行连接。如下图所示:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第9张图片

[](()Jedis使用Sentinel机制

下面就是一个使用Jedis客户端使用Sentinel机制的例子。

1、引入pom依赖:

redis.clients

jedis

2.9.0

compile

2、新建一个测试类TestJedisSentinel进行测试:

package com.lonelyWolf.redis.sentinel;

import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;

import java.util.Set;

public class TestJedisSentinel {

private static JedisSentinelPool pool;

private static JedisSentinelPool initJedisSentinelPool() {

// master的名字是sentinel.conf配置文件里面的名称

String masterName = “mymaster”;

Set sentinels = new HashSet();

sentinels.add(“xx.xxx.xxx.xxx:26380”);

// sentinels.add(“xx.xxx.xxx.xxx:26381”);

// sentinels.add(“xx.xxx.xxx.xxx:26382”);

pool = new JedisSentinelPool(masterName, sentinels);

return pool;

}

public static void main(String[] args) {

JedisSentinelPool pool = initJedisSentinelPool();

pool.getResource().set(“name”, “longly_wolf”);

System.out.println(pool.getResource().get(“name”));

}

}

连接时需要把所有Sentinel的连接建立并放入池内,然后客户端会遍历其中所有服务,找到第一个可用的Sentinel服务,并获取到master服务器的地址,然后建立连接。

[](()SpringBoot使用Sentinel机制

如果使用SpringBoot,则需加入以下两个配置:

spring.redis.sentinel.master=mymaster

spring.redis.sentinel.nodes=ip:port,ip:port,ip:port

第一个参数sentinel.conf中自定义的名字,第二个参数需要配置所有的Sentinel服务器ip和端口信息。

[](()Sentinel机制的不足之处

哨兵机制虽然实现了高可用性,但是仍然存在以下不足:

  • 1、主从切换的过程中会丢失数据,因为只有一个 master,所以在切换过程中服务是不可用的。

  • 2、哨兵机制其本质还是master-slave集群,即:1主N从。也就是master服务器依然只有1个,并没有实现水平扩展。

[](()Redis分布式集群方案


要实现一个Redis水平扩展,需要实现分片来进行数据共享,可以有三种思路:

  • 1、在客户端实现相关的逻辑,由客户端实现分片决定路由到哪台服务器。

  • 2、将分片处理的逻辑运行一个独立的中间服务,客户端连接到这个中间服务,然后由中间服务做请求的转发。

  • 3、基于服务端实现。

[](()客户端实现分片

客户端实现分片的话,Jedis提供了这个分片(Sharding)功能:

package com.lonelyWolf.redis.cluster.client;

import redis.clients.jedis.*;

import java.util.Arrays;

import java.util.List;

/**

  • Jedis客户端实现集群分片功能

*/

public class TestJedisSharding {

public static void main(String[] args) {

JedisPoolConfig poolConfig = new JedisPoolConfig();

//创建所有的连接服务分片

JedisShardInfo shardInfo1 = new JedisShardInfo(“xx.xxx.xxx.xxx”, 6370);

JedisShardInfo shardInfo2 = new JedisShardInfo(“xx.xxx.xxx.xxx”, 6371);

//将所有连接加入连接池

List shardInfoList = Arrays.asList(shardInfo1, shardInfo2);

ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, shardInfoList);

//创建10个key值存入服务器

ShardedJedis jedis = jedisPool.getResource();//获取连接

for (int i=1;i<=10;i++){

jedis.set(“name” + i,“lonely_wolf” + i);

}

//取出key和其所在服务器信息

for (int i=1;i<=10;i++){

String key = “name” + i;

Client client = jedis.getShard(“name”+i).getClient();

System.out.println(“key值:” + jedis.get(key) + “,存在于服务器的端口为:” + client.getPort());

}

}

}

输出结果如下:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第10张图片

然后我们分别去服务器上看一下可以看到是完全匹配的:

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第11张图片

【Redis系列9】手把手带你搭建单机版高可用分布式Redis集群(Cluster)_第12张图片

[](()客户端分片的缺陷

客户端实现分片的好处就是配置简单,而且分片规则都是由客户端来实现,但是却存在以下缺点:

  • 1、客户端需要支持分片,假如代码移植到其他项目,而其他项目使用的Redis客户端不支持分片,那就会造成集群不可用。

  • 2、客户端分片模式下不能很好地实现服务器动态增减。

[](()中间代理服务实现分片

中间代理服务实现分片其实就是将客户端的分片逻辑进行抽取,然后单独部署成一个服务,这样做的好处是客户端不需要处理分片逻辑,而且也不用关心服务器的增减。

中间代理服务分片的方案有两个使用比较广泛,那就是(这两种方案如果有兴趣的可以点击对应的链接进去github获取到对应的源码进行了解):

  • 1、[Twemproxy](()(Twitter开源)。

  • 2、[Codis](()(豌豆荚开源)

使用中间服务的最大缺陷就是其本身是独立的服务,而为了保证高可用,也需要对这个中间服务进行高可用的集群配置,所以会导致整个系统架构更加复杂。

[](()Redis Cluster方案

Redis Cluster是Redis3.0版本正式推出的,实现了高可用的分布式集群部署。

你可能感兴趣的:(Java,经验分享,面试,java)