Redis 集群之 Redis Cluster 原理及安装部署

主从架构和哨兵架构的缺点

我们知道 Redis 主从架构和哨兵架构可以通过扩容从节点增加 QPS,但是如果需要缓存的数据有上百个 G 的话,在主从架构和哨兵架构下因为是读写分离,主节点写入数据,从节点从主节点复制数据,此时单机是无法存储这个么数据了呢。那如果才能解决这个问题呢?如何才能突破单机的瓶颈呢。Redis 官方给出的答案就是 —— Redis Cluster。

Redis Cluster 介绍与原理

可以支撑 N 个 master node ,每个 master node 可以挂载多个 slave node。如果某个 master 节点挂掉,可以自动的把它其中的一个 slave node 升级为 master node,从而实现了高可用。实现了读写分离的架构,在 master 节点写,在 slave 节点读。
其主要应用于海量数据、高并发、高可用的场景,如果只有几个 G 的数据需要缓存的话,单机完全可以应付,使用 主从架构 + 哨兵架构即可以满足需要。

提供内置的高可用支持,部分 master node 不可用的情况下,还可以继续工作。

Redis Cluster 的数据分片算法

对于主从架构,只有一个 master 节点,只要往这一个节点上写数据就可以呢。现在 Redis Cluster 有多个 master 节点,那是如何把数据分配到不同的 master node 上,并尽量使每个节点保存的数据均衡呢?

Redis Cluster 使用了 hash slot 算法来实现数据分片。

不同与一般的普通的 hash 算法及一致性 hash 算法, hash slot 算法 有固定的 16384 个 hash slot,其会对需要存入 redis 的每个 key 计算 CRC16 值,然后对 16384 取模,从而获得 key 对应的 hash slot 值。

Redis Cluster 中每个 master node 会持有一部分的 slot ,比如有 3 个 master node ,每个 master node 会持有大概 5000 多个的 hash slot。每增加一个 master node,就把一部分的 hash slot 移动到新增的节点,如果删除某一个 master node,就把它的 hash slot 移动到其它的 master node 上。

同时,我们可以对指定的数据,通过 hash tag 可以让它们走同一个 hash slot。

跳转

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

worker-01:7000> get key1
(error) MOVED 9189 192.168.56.102:7000
worker-01:7000> 

看上面的示例,会提示报错,MOVED指令的第一个参数 9189 是 key 对应的槽位,后面是目标的节点地址。客户端在收到 MOVED 指令后,要立即纠正本地的槽位映射表,后续所有的 key 将使用新的槽位映射表。

在我们使用 redis-cli 操作时,我肯定不希望每次还需要自己去重新连接新的节点去获得 key 对应的值。可以通过在连接的时候增加 -c 可以实现自动定位到新的节点去获得相应的值。

[root@bogon src]# ./redis-cli -c -h  worker-01 -p 7000
worker-01:7000> get key1
-> Redirected to slot [9189] located at 192.168.56.102:7000
"nihao"
192.168.56.102:7000>

迁移

Redis Cluster 提供了工具 redis-trib 可以让运维人员手动调整槽位的分配情况,可以通过组合各种原生的 Redis Cluster 指令来实现。

Redis 迁移的最小单位是槽,Redis 一个槽一个槽地进行迁移,当一个槽正在迁移时,这个槽就处于中间过渡状态。Redis-trib 首先会在源节点和目标节点设置好中间过渡状态,然后一次性获取源节点槽位的所有 key 列表,再挨个 key 进行迁移。每个 key 的迁移过程是以源节点作为目标节点的 “客户端”,源节点对当前的 key 执行 dump 指令得到序列化内容,然后通过 “客户端” 向目标节点发送 restore 指令携带序列化内容作为参数,目标节点再进行反序列化就可以将内容恢复到目标节点的内存中,然后返回 “客户端” OK,源节点“客户端”收到后再把当前节点的 key 删除掉就完成了单个 key 的迁移的全过程。

大致流程就是:从源节点获得内容->存到目标节点->从源节点删除内容。

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

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

如果每个 key 的内容很小,迁移会很快,几乎不会影响客户端的正常访问。如果 key 的内容很大,因为迁移是阻塞的,会同时造成源节点和目标节点卡顿,影响集群的稳定性。所以在集群环境下,业务逻辑要尽可能避免产生很大的 key。

在迁移过程中,客户端的访问的流程会发生很大的变化 。

首先,新旧两个节点对应的槽位都存大部分 key 数据。客户端先尝试访问旧节点,如果对应的数据还在旧节点里面,那么旧节点正常处理。如果对应的数据不在旧的节点里面,那么有两种可能,要么该数据在新节点里面,要么根本不存在。旧节点不知道是哪种情况,所以它会向客户端返回一个 ask targetNodeAddr 的重定向指令,客户端收到这个重定向指令后,先去目标节点执行一个不带任何参数的 asking 指令,然后在目标节点再重新执行原先的操作指令。

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

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

从以上过程可以看出,迁移是会影响服务效率的,同样的指令在正常情况下一个 ttl 就能完成,而在迁移过程下需要 3 个 ttl 才能搞定。

容错

Redis Cluster 可以为每个主节点设置若干个从节点,当主节点发生故障时,会自动将一个从节点提升为主节点。如果某个主节点没有从节点时,那么发生故障时,整个集群将完全处于不可用状态。 当然,Redis 也提供了一个参数 cluster-node-require-full-coverage 可以允许部分节点发生故障时,其它节点还可以继续对外访问。

网络抖动问题

生产环境有很多因素可能出现突然间网络中断的情况,然后很快又恢复。Redis Cluster 提供了一个参数 cluster-node-timeout,表示当某个节点持续 timeout 的时间无法连接时,才会被认为出现了故障,才需要进行主备切换。

可能下线与确认下线

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

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

集群变更通知

当服务器节点发生变更时,客户端应该立即得到通知以实时刷新自己节点关系表。分二种情况:

  • 目标节点挂掉后,客户端会抛出一个 ConnectionError,紧接着会随机挑一个节点进行重试。这时被重试的节点会通过 MOVED 指令告知目标槽位被分配到新的节点地址。

  • 运维手动修改了集群信息,将主节点切换到其它节点,并将旧的节点移出集群。这时打到旧的节点的指令会收到一个 ClusterDown 的错误,告知当前节点所在集群不可用(当前节点已被删除,它不再属于之前的集群)。这时客户端会关闭所有的连接,清空槽位映射关系表,然后向上层抛错,待一条指令过来时,就会重新尝试初始化节点信息。

槽位迁移感知

如果 Redis Cluster 中某个槽位正在迁移或者已经迁移完毕,那么客户端如何能感知到这种变化呢?客户端保存了槽位和节点的映射关系表,它需要及时得到更新,才可以正常的将指令发到正确的节点中。

这里需要用到我们之前提到的二个指令,MOVED 和 ASKING。

MOVED 指令用来纠正槽位的。如果我们将指令发送到错误的节点,该节点会发现对应的槽位不归自己管理,就会将目标节点的地址随同 MOVED 指令回复到客户端并通知客户端去目标节点去访问。这个时候客户端就会刷新自己的槽位关系表,然后重试指令,后续所有打在该槽位的指令都会转到目标节点。

ASKING 指令 和 MOVED 指令不一样,这是用来临时纠正槽位的。如果当前槽位正处于迁移中,指令会先被发送到槽位所在的旧节点。如果旧节点存在数据,那就直接返回结果。如果不存在数据,那么数据可能真的不存在,也可能在迁移目标节点上,所以旧的节点会通知客户端去新的节点尝试拿数据,看看新节点有没有数据。这时,就时就会给客户端返回一个 asking error 并携带上目标节点的地址。客户端收到这个 asking error 后,就会去目标节点尝试。客户端不会刷新刷新槽位映射关系表,因为它只是临时纠正该指令的槽位信息,不影响后续指令。

  • 重试二次

MOVED 和 ASKING 指令都是重试指令,客户端会因为这两个指令多重试一次。那有没有可能会重试二次呢?

是存在,如果一条指令被发送到错误的节点,这个节点会先给你一个 MOVED 错误告知你去另外的节点重试,所以客户端就去另外节点重试了,结果刚好这个时候运维人员要对这个槽位进行迁移操作,于是给客户端回复了一个 ASKING 指令告知客户端去目标节点去重试指令,所以这个时间会重试了二次。

  • 重试多次

重试多次也是存在的,所以一般都会设置一个最大重试次数,超过次数则向上抛出异常。

安装部署

对于 5.0 之前的版本来说,需要安装 ruby 才能安装 redis cluster 集群,但是从 5.0 之后就不需要了。

我们这理准备安装一个 3 master node 和 3 slave node 的一个集群。分别部署在 3 台机器上,即每台机器有 2 个节点。

准备

需要准备好三台服务器,其 IP 映射分别是:

192.168.56.101 worker-01        worker-01.joyxj.com
192.168.56.102 worker-02        worker-02.joyxj.com
192.168.56.103 worker-03        worker-03.joyxj.com

同时下载好最新的 Redis,笔者下载是 redis-5.0.5,放在目录 /opt/tools,这个大家可以随意。

配置文件

  1. 新建目录 /etc/redis/cluster 目录用于存在配置文件。

  2. 拷贝配置文件到目录下 /etc/redis/cluster,由于我们需要在一台服务器上启动二个 redis 实例,所以拷贝配置文件二次,并且重命名为 7000.conf7001.conf。后面我们会用 70007001 端口启动 reids 实例。

    cp /opt/tools/redis-5.0.5/redis.conf /etc/redis/cluster/7000.conf
    cp /opt/tools/redis-5.0.5/redis.conf /etc/redis/cluster/7001.conf
    

    分别配置这二个文件:

    7000.conf

    port 7000
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    dir /var/redis/7000
    daemonize yes
    # 按实际服务器 配置,也可以直接配置 IP
    bind worker-01
    

    7001.conf

    port 7001
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    dir /var/redis/7001
    daemonize yes
    # 按实际服务器 配置,也可以直接配置 IP
    bind worker-01
    

    三台服务器都需要按照上面操作。

启动实例

[root@bogon redis-5.0.5]# /opt/tools/redis-5.0.5/src/redis-server  /etc/redis/cluster/7000.conf
[root@bogon redis-5.0.5]# /opt/tools/redis-5.0.5/src/redis-server  /etc/redis/cluster/7001.conf

三台服务器都启动实例。

创建集群

[root@bogon redis-5.0.5]#  /opt/tools/redis-5.0.5/src/redis-cli --cluster create 192.168.56.101:7000 192.168.56.101:7001 \
192.168.56.102:7000 192.168.56.102:7001 192.168.56.103:7000 192.168.56.103:7001 \
--cluster-replicas 1

这里好像只能使用 IP 地址。

nodes.conf

在配置文件中我们配置了一个 cluster-config-file nodes.conf 参数,我们来看下这个文件写了什么内容。
我们选择其中的一个文件,找到所在的文件目录 var/redis/7000/nodes.conf

  fe507ba7dcfe613293bfa0553d19cd34cfccbd86 192.168.56.102:7000@17000 master - 0 1564327716585 3 connected 5461-10922
  229c88ce83db7ac97e0b5887dcf2f642d34d9483 192.168.56.101:7001@17001 master - 0 1564327716000 7 connected 10923-16383
  93e319527a0d2d6eb89151f3586ca1d182abf6c8 192.168.56.102:7001@17001 slave c29dc2a0bf90de3e01f6a8d89288088bb1a2ad69 0 1564327717594 4 connected
  d2a50194481c0b08956bf56e9692bec654842cc6 192.168.56.103:7000@17000 slave 229c88ce83db7ac97e0b5887dcf2f642d34d9483 0 1564327717391 7 connected
  dd4c8528f2f83af7e1e7f02fbe634ae3973733ce 192.168.56.103:7001@17001 slave fe507ba7dcfe613293bfa0553d19cd34cfccbd86 0 1564327717594 6 connected
  c29dc2a0bf90de3e01f6a8d89288088bb1a2ad69 192.168.56.101:7000@17000 myself,master - 0 1564327716000 1 connected 0-5460
  vars currentEpoch 7 lastVoteEpoch 7

文件中写入了每个节点是 master node 还是 slave node ,以及是否连接,同时对于 master 节点还记录该节点记录哪些槽的数据。

查看集群状态。

# 用 redis-cli 工具,随便连接上一台服务器
redis-cli -h worker-01 -p 7000
worker-01:7000> cluster nodes

可以用过 cluster help 查看更多命令。


   转载规则


《Redis 集群之 Redis Cluster 原理及安装部署》 孤独如梦 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
OpenResty(nginx) + keepliaved 实现站点高可用 OpenResty(nginx) + keepliaved 实现站点高可用
前言利用 Nginx 的负载均衡和反向代理,我们可以实现后台应用的高可用。这时候的用户请求是先通过 nginx, 然后在访问我们的应用,如果 nginx 挂掉了,那我们所有的应用都无法访问了。所以我们必须实现 Nginx 的高可用,达到一台
2019-08-01
下一篇 
Redis 集群之主备架构(哨兵机制)原理及安装部署 Redis 集群之主备架构(哨兵机制)原理及安装部署
前言之前的文章介绍(Redis 集群之主从架构原理及安装部署)主从架构会有这样一个问题,如果 master node 因为某些原因宕机了,那么整个 redis 集群就无法进行写操作了。需要运维人员手动进行切换,然后所有程序需要修改配置然后重
2019-07-20
  目录