Redis分布式集群理论与实践

摘要

在《Redis Sentinel看完这篇就够了》这篇我们已经讲过了Sentinel的理论和实践了,今天我们再来看看 Redis Cluster (即Redis集群)相关的一下内容。本篇没有涉及复杂难懂的分布式概念的赘述,只是提供了从用户角度如何搭建测试以及使用的方法。

前提须知

了解Redis Cluster之前,你需要理解分布式系统的CAP理论,这里简单描述一下:

  • C - Consistent ,一致性

  • A - Availability ,可用性

  • P - Partition tolerance ,分区容忍性

分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个叫做 「网络分区」

所以说,分布式系统一点要满足分区容忍性

在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操 作将无法同步到另外一个节点,所以数据的 「一致性」 将无法满足,因为两个分布式节点的 数据不再保持一致。除非我们牺牲 「可用性」 ,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。

所以说,网络分区发生时,一致性和可用性两难全

Redis Cluster为了保证 「可用性」 ,选择牺牲 「一致性」 ,采用相对较弱的 「最终一致性」

理论篇

分片

Redis Cluster是一个提供在多个Redis节点间共享数据的集群。它没有使用一致性hash,而是引入了 哈希槽的概念。集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽(crc16(key) % 16384)。

举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽。
  • 节点 B 包含5501 到 11000 号哈希槽。
  • 节点 C 包含11001 到 16384号哈希槽。

如果想新添加个节点D,需要将节点 A, B,C中部分槽移到D上。 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可。

迁移

Redis分布式集群理论与实践_第1张图片

Redis迁移是以槽为单位的,一个槽进行迁移时,就处于中间过渡状态。这个槽在源节点A的状态为 migrating,在目标节点B的状态为 importing,表示数据正在从源流向目标。

迁移工具 redis-trib 首先会在源和目标节点设置好中间过渡状态,然后一次性获取源节 点槽位的所有 key 列表(keysinslot 指令,可以部分获取),再挨个 key 进行迁移。

从源节点获取内容 => 存到目标节点 => 从源节点删除内容

注意这里的迁移过程是同步的,在目标节点执行 restore 指令到原节点删除 key 之间,原节点的主线程会处于阻塞状态,直到 key 被成功删除。

如果迁移过程中突然出现网络故障,整个 slot 的迁移只进行了一半。这时两个节点依旧 处于中间过渡状态。待下次迁移工具重新连上时,会提示用户继续进行迁移。

在迁移过程中,客户端访问的流程会有很大的变化:

  1. 客户端先尝试访问旧节点,如果对 应的数据还在旧节点里面,那么旧节点正常处理。
  2. 如果对应的数据不在旧节点里面,那么有两种可能,要么该数据在新节点里,要么根本就不存在。旧节点不知道是哪种情况,所以它会向客户端返回一个-ASK targetNodeAddr 的重定向指令。
  3. 客户端收到这个重定向指令后,先去目标节点执行一个不带任何参数的 asking 指令。
  4. 目标节点返回OK给客户端。
  5. 客户端重新向目标节点访问数据。

为什么需要执行一个不带参数的 asking 指令呢?

因为在迁移没有完成之前,按理说这个槽位还是不归新节点管理的,如果这个时候向目标节点发送该槽位的指令,节点是不认的,它会向客户端返回一个-MOVED 重定向指令告诉它去源节点去执行。如此就会形成重定向循环。asking 指令的目标就是打开目标节点的选项,告诉它下一条指令不能不理,而要当成自己的槽位来处理。

moved 和 asking 指令都是重试指令,客户端会因为这两个指令多重试一次。

跳转

客户端为了可以直接定位某个具体的 key 所在的节点,它就需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。

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

GET x
-MOVED 3999 127.0.0.1:6381

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

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

容错

Redis Cluster 可以为每个主节点设置若干个从节点,单主节点故障时,集群会自动将其 中某个从节点提升为主节点。如果某个主节点没有从节点或者从节点全部不可用,那么当它发生故障时,集群将完全处于不可用状态。

网络抖动

为解决网络抖动问题,Redis Cluster 提供了一种选项 cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换。

可能下线与确定下线

与Sentinel类似,Cluster判定节点下线与要经过协商,只有当大多数节点都认定了某个节点失联了,集群才认为该节点需要进行主从切换来容错。Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。

实践篇

环境

  • CentOS 7
  • Redis4.0.14
  • 虚拟机

注意,Cluster适用于Redis3.0(包括3.0)以上版本。

部署架构图

Redis分布式集群理论与实践_第2张图片

部署过程

首先, 让我们进入一个新目录, 并创建六个以端口号为名字的子目录, 稍后我们在将每个目录中运行一个 Redis 实例:

mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005

在文件夹 7000 至 7005 中, 各创建一个 redis.conf 文件, 文件的内容可以使用下面的示例配置, 但记得将配置中的端口号从 7000 改为与文件夹名字相同的号码。

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

在cluster-test文件夹中下载并安装redis,下载安装过程可参考《Redis Sentinel看完这篇就够了》。

安装好以后使用前面创建好的redis.conf 文件启动redis,下面给出7000的样例:

cd 7000
../redis-server ./redis.conf

现在我们已经有了六个正在运行中的 Redis 实例, 接下来我们需要使用这些实例来创建集群, 并为每个节点编写配置文件。通过使用 Redis 集群命令行工具 redis-trib , 编写节点配置文件的工作可以非常容易地完成。 redis-trib 位于 Redis 源码的 src 文件夹中。

./redis-trib.rb create --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

选项 –replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yes , redis-trib 就会将这份配置应用到集群当中,让各个节点开始互相通讯,最后可以得到如下信息:

[OK] All 16384 slots covered

这表示集群中的 16384 个槽都有至少一个主节点在处理, 集群运作正常。

测试分片存储

我们将使用 redis-cli 为例来进行演示:

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

测试故障转移

要触发一次故障转移, 最简单的办法就是令集群中的某个主节点进入下线状态。首先用以下命令列出集群中的所有主节点:

$ redis-cli -p 7000 cluster nodes | grep master
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385482984082 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 master - 0 1385482983582 0 connected 11423-16383
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422

通过命令输出得知端口号为 7000 、 7001 和 7002 的节点都是主节点, 然后我们可以通过向端口号为7002 的主节点发送 DEBUG SEGFAULT 命令, 让这个主节点崩溃:

$ redis-cli -p 7002 debug segfault
Error: Server closed the connection

使用 cluster nodes 命令,查看集群在执行故障转移操作之后, 主从节点的布局情况:

$ redis-cli -p 7000 cluster nodes
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385503418521 0 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385503419023 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385503419023 3 connected 11423-16383
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385503417005 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385503418016 3 connected

现在masters运行在 7000, 7001 和 7005端口上。原来的master 7002现在变成了一个7005的一个从节点。

参考

[1] Redis cluster tutorial.

[2] Redis深度历险:核心原理和应用实践.

你可能感兴趣的:(Redis)