Redis:redis-cluster分布式解决方案

一、redis-cluster 介绍

redis-cluster介绍可以从redis中文或者英文官网中详细看到,我这里只选择我认为重点的来说

1. 键分布模型

(1) 普通模型

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

HASH_SLOT = CRC16(key) mod 16384

(2) Key Hash Tags模型

计算哈希槽可以实现哈希标签(hash tags),但这有一个例外。哈希标签是确保两个键都在同一个哈希槽里的一种方式。将来也许会使用到哈希标签,例如为了在集群稳定的情况下(没有在做碎片重组操作)允许某些多键操作。

为了实现哈希标签,哈希槽是用另一种不同的方式计算的。基本来说,如果一个键包含一个 “{…}” 这样的模式,只有 { 和 } 之间的字符串会被用来做哈希以获取哈希槽。但是由于可能出现多个 { 或 },
计算的算法如下:

  • 如果键包含一个 { 字符。
  • 那么在 { 的右边就会有一个 }。
  • 在 { 和 } 之间会有一个或多个字符,第一个 } 一定是出现在第一个 { 之后。
  • 然后不是直接计算键的哈希,只有在第一个 { 和它右边第一个 } 之间的内容会被用来计算哈希值。

例子:

  • 比如这两个键 {user1000}.following 和 {user1000}.followers 会被哈希到同一个哈希槽里,因为只有 user1000 这个子串会被用来计算哈希值。
  • 对于 foo{}{bar} 这个键,整个键都会被用来计算哈希值,因为第一个出现的 { 和它右边第一个出现的 } 之间没有任何字符。
  • 对于 foo{{bar}}zap 这个键,用来计算哈希值的是 {bar 这个子串,因为它是第一个 { 及其右边第一个 } 之间的内容。
  • 对于 foo{bar}{zap} 这个键,用来计算哈希值的是 bar 这个子串,因为算法会在第一次有效或无效(比如中间没有任何字节)地匹配到 { 和 } 的时候停止。
  • 按照这个算法,如果一个键是以 {} 开头的话,那么就当作整个键会被用来计算哈希值。当使用二进制数据做为键名称的时候,这是非常有用的。

2. MOVED 重定向

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。

GET x
-MOVED 3999 127.0.0.1:6381

MOVED 指令的第一个参数 3999 是 key 对应的槽位编号,后面是目标节点地址。
MOVED 指令前面有一个减号,表示该指令是一个错误消息。

注意:客户端收到 MOVED 指令后,要立即纠正本地的槽位映射表。后续所有 key 将使用新的槽位映射表

3. ASK 重定向

(1) key 迁移过程

Redis:redis-cluster分布式解决方案_第1张图片

(2) ASK重定向机制

在key迁移过程中,为什么我们不能单纯地使用 MOVED 重定向呢?因为当我们使用 MOVED 的时候,意味着我们认为哈希槽永久地被另一个不同的节点处理,并且希望接下来的所有查询都尝试发到这个指定的节点上去。而 ASK 意味着我们只要下一个查询发送到指定节点上去。

这个命令是必要的,因为下一个关于哈希槽 8 的查询需要的键或许还在节点 A 中,所以我们希望客户端尝试在节点 A 中查找,如果需要的话也在节点 B 中查找。 由于这是发生在 16384 个槽的其中一个槽,所以对于集群的性能影响是在可接受的范围。

然而我们需要强制客户端的行为,以确保客户端会在尝试 A 中查找后去尝试在 B 中查找。如果客户端在发送查询前发送了 ASKING 命令,那么节点 B 只会接受被设为 IMPORTING 的槽的查询。 本质上来说,ASKING 命令在客户端设置了一个一次性标识(one-time flag),强制一个节点可以执行一次关于带有 IMPORTING 状态的槽的查询。

所以从客户端看来,ASK 重定向的完整语义如下:

  • 如果接受到 ASK 重定向,那么把查询的对象调整为指定的节点。
  • 先发送 ASKING 命令,再开始发送查询。
  • 现在不要更新本地客户端的映射表把哈希槽 8 映射到节点 B。

一旦完成了哈希槽 8 的转移,节点 A 会发送一个 MOVED 消息,客户端也许会永久地把哈希槽 8 映射到新的 ip:端口号 上。 注意,即使客户端出现bug,过早地执行这个映射更新,也是没有问题的,因为它不会在查询前发送 ASKING 命令,节点 B 会用 MOVED 重定向错误把客户端重定向到节点 A 上。

4. redis-cluster 失效检测(Failure detection)

(1) 可能下线 (PFAIL-Possibly Fail) 与确定下线 (Fail)

因为 Redis Cluster 是去中心化的,一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。所以集群还得经过一次协商的过程,只有当大多数节点都认定了某个节点失联了,集群才认为该节点需要进行主从切换来容错。

Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换。

(2) PFAIL转换为Fail条件

PFAIL状态条件:

  • 某个节点,我们称为节点 A,标记另一个节点 B 为 PFAIL。
  • 节点 A 通过 gossip 字段收集到集群中大部分主节点标识的节点 B 的状态信息。
  • 大部分主节点在 NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT 这个时间内标记节点 B 为 PFAIL或者FAIL 状态

如果以上所有条件都满足了,那么节点 A 会:

  • 标记节点 B 为 FAIL。
  • 向所有可达节点发送一个 FAIL 消息。

FAIL 消息会强制每个接收到这消息的节点把节点 B 标记为 FAIL 状态。

5. 丛节点的选举和提升

从节点的选举和提升都是由从节点处理的,主节点会投票要提升哪个从节点。一个从节点的选举是在主节点被至少一个具有成为主节点必备条件的从节点标记为 FAIL 的状态的时候发生的。

当以下条件满足时,一个从节点可以发起选举:

  • 该从节点的主节点处于 FAIL 状态。
  • 这个主节点负责的哈希槽数目不为零。
  • 从节点和主节点之间的重复连接(replication link)断线不超过一段给定的时间,这是为了确保从节点的数据是可靠的。
  • 一个从节点想要被推选出来,那么第一步应该是提高它的 currentEpoch 计数,并且向主节点们请求投票。

6. 主节点回复从节点的投票请求

主节点接收到来自于从节点、要求以 FAILOVER_AUTH_REQUEST 请求的形式投票的请求。
要授予一个投票,必须要满足以下条件:

  • 在一个给定的时段(epoch)里,一个主节点只能投一次票,并且拒绝给以前时段投票:每个主节点都有一个 lastVoteEpoch 域,一旦认证请求数据包(auth request packet)里的 currentEpoch 小于 lastVoteEpoch,那么主节点就会拒绝再次投票。当一个主节点积极响应一个投票请求,那么 lastVoteEpoch 会相应地进行更新。
  • 一个主节点投票给某个从节点当且仅当该从节点的主节点被标记为 FAIL。
  • 如果认证请求里的 currentEpoch 小于主节点里的 currentEpoch 的话,那么该请求会被忽视掉。因此,主节点的回应总是带着和认证请求一致的 currentEpoch。如果同一个从节点在增加 currentEpoch 后再次请求投票,那么保证一个来自于主节点的、旧的延迟回复不会被新一轮选举接受。

7. 备份迁移算法

(1) 问题引入

Redis 集群实现了一个叫做备份迁移(replica migration)的概念,以提高系统的可用性。在集群中有主节点-从节点的设定,如果主从节点间的映射关系是固定的,那么久而久之,当发生多个单一节点独立故障的时候,系统可用性会变得很有限。
比如:

  • 主节点 A 有且只有一个从节点 A1。
  • 主节点 A 失效了。A1 被提升为新的主节点。
  • 三个小时后,A1 因为一个独立事件(跟节点 A 的失效无关)失效了。由于没有其他从节点可以提升为主节点(因为节点 A 仍未恢复正常),集群没法继续进行正常操作。

(2) 备份迁移算法

算法具体可以参考官网。例如,如果有 10 个主节点,它们各有 1 个从节点,另外还有 2 个主节点,它们各有 5 个从节点。会尝试迁移的从节点是在那 2 个拥有 5 个从节点的主节点中的所有从节点里,节点 ID 最小的那个

例如,假设集群有三个主节点 A,B,C。节点 A 和 B 都各有一个从节点,A1 和 B1。节点 C 有两个从节点:C1 和 C2。
备份迁移是从节点自动重构的过程,为了迁移到一个没有可工作从节点的主节点上。在上面提到的例子中,备份迁移过程如下:

  • 主节点 A 失效。A1 被提升为主节点。
  • 节点 C2 迁移成为节点 A1 的从节点,要不然 A1 就没有任何从节点。
  • 三个小时后节点 A1 也失效了。
  • 节点 C2 被提升为取代 A1 的新主节点。
  • 集群仍然能继续正常工作。

二、redis-cluster 配置

1. 下载源码包

从官网下载redis 6.0.6 版本的源码包,解压并且进入src,然后make,将可执行程序拷贝出来使用即可.
下载地址:redis.tar.gz

shell> cd src
shell> make

2. redis-cluster配置

//1. 创建redis 实例
shell> mkdir -p 7000/data 7001/data 7002/data 7003/data 7004/data 7005/data
shell> cd ./7000
shell> vim redis.conf

//配置项内容
port 7000(每个节点的端口号)
daemonize yes
bind 127.0.0.1(绑定当前机器 IP)
dir /home/ubuntu/Software/redis/redis-cluster/7000/data(数据文件存放位置)
pidfile /home/ubuntu/Software/redis/redis-cluster/7000/redis_7000.pid(pid 7000和port要对应)
cluster-enabled yes(启动集群模式)
cluster-config-file nodes-7000.conf(7000和port要对应)
cluster-node-timeout 15000
appendonly yes

//2. 复制到其他文件夹,并把7000改为对应的端口号
shell> cp ./7000/redis.conf ./7001/
shell> cp ./7000/redis.conf ./7002/
shell> cp ./7000/redis.conf ./7003/
shell> cp ./7000/redis.conf ./7004/
shell> cp ./7000/redis.conf ./7005/
//vim 更改对应项
...

//3. 启动单个实例
shell> ./redis-server ../7000/redis.conf 
shell> ./redis-server ../7001/redis.conf 
shell> ./redis-server ../7002/redis.conf 
shell> ./redis-server ../7003/redis.conf 
shell> ./redis-server ../7004/redis.conf 
shell> ./redis-server ../7005/redis.conf 

//4. 将单点实例整合成一个redis-cluster集群
shell> ./redis-cli --cluster help
shell> ./redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
//如下部分内容:
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7004 to 127.0.0.1:7000
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7003 to 127.0.0.1:7002
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 8fe7bc33040f230c998410804aad5939effcf37d 127.0.0.1:7000
   slots:[0-5460] (5461 slots) master
M: 771b4eb970a567e027fe5294075580f83a47865e 127.0.0.1:7001
   slots:[5461-10922] (5462 slots) master
M: eb3de457df241c38da2286c5d5734600a3a5ced2 127.0.0.1:7002
   slots:[10923-16383] (5461 slots) master
S: 36b5eae11fca4f42e8c14394f0b3c33609c6309e 127.0.0.1:7003
   replicates 8fe7bc33040f230c998410804aad5939effcf37d
S: 852f5790bdad1e06e795b63f5247821adbdb747d 127.0.0.1:7004
   replicates 771b4eb970a567e027fe5294075580f83a47865e
S: 2154dab30331e6f5b6e0a8c43b70e98f21b54a19 127.0.0.1:7005
   replicates eb3de457df241c38da2286c5d5734600a3a5ced2
...

后台进程如下图所示:
Redis:redis-cluster分布式解决方案_第2张图片

3. redis-cluster使用展示

//redis-cli 使用
shell> ./redis-cli -p 7000
127.0.0.1:7000> get foo
(error) MOVED 12182 127.0.0.1:7002
shell> ./redis-cli -p 7002
127.0.0.1:7002> get foo
(nil)
127.0.0.1:7002> set foo 1
OK
127.0.0.1:7002> get foo
"1"

//python3命令行使用展示 (ubuntu 18.04需要安装:python3-redis python3-rediscluster)
shell> python
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from rediscluster import StrictRedisCluster
>>> startup_nodes = [{"host": "127.0.0.1", "port": "7002"}]
>>> rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True)
>>> print(rc.get("foo"))
1
>>> rc.set("foo","2")
True
>>> print(rc.get("foo"))
2

你可能感兴趣的:(数据库,redis,分布式,哈希算法)