0%

手把手教你理解 Redis Cluster

关于 Redis 运维 系列其他文章的传送门

  1. 《 我不要你觉得!我要我觉得!学习 Redis 中实用的命令和配置,就看这篇! 》

  2. 《 轻松搞定 Redis 的主从复制和高可用 》

  3. 《 手把手教你理解 Redis Cluster 》      您当前所在位置


关于 Redis 开发 系列其他文章的传送门

  1. 《 Python 和 Redis 的那点事 》

  2. 《 用了 Scrapy-Redis 分布式爬虫后,一个能打的都没有! 》



logo

一、Redis 主从复制架构面临的问题

当 Redis 是主从复制架构时,客户端需要知道在读、写时语句分别发往哪个节点,并且写语句要均衡的发往各个从节点。如果客户端无法做到这点,这时就要使用一个可以理解语句含义的代理,也就是“语句路由器”来帮助客户端完成请求工作,就像 ProxySQL一样。

以 MariaDB 举例,为了防止单点故障,前端的语句路由器就要使用 Keepalived 等工具实现高可用,这就增加了集群架构的复杂度。所以很多中小企业在使用 MariaDB 时,会部署为 Galera Cluster 的方式,使得每个节点都是主节点,都可读可写,然后前端只需要加一个简单的反向代理即可,例如 Nginx 或者 Proxy。不过很遗憾 Redis 不支持这种多主模型。

一些 Redis 可以使用的“语句路由器”

  • Twemproxy (Twitter):即能代理 Redis,也能代理 Memcached;

    • 使用代理分片机制;
    • 优点:
      • 非常稳定,企业级方案;
    • 缺点:
      • 单点故障;
      • 需依賴第三方软件,如 Keepalived;
      • 无法平滑地橫向扩展;
      • 沒有后台界面;
      • 代理分片机制引入更多的来回次数并提高延迟;
      • 单核模式,无法充份利用多核,除非多实例;
      • Twitter 官方內部不再继续使用 Twemproxy;
  • Codis (豌豆荚)

    • 代理分片机制;
    • 2014年11月开源;
    • 基于 Go 以及 C 语言开发;
    • 优点:
      • 非常穩定,企业级方案;
      • 数据自动平衡;
      • 高性能;
      • 简单的测试显示较 Twemproxy 快一倍;
      • 善用多核 CPU;
      • 简单;
      • 沒有 Paxos 类的协调机制;
      • 沒有主从复制;
      • 有后台界面;
    • 缺点:
      • 代理分片机制引入更多的来回次数并提高延迟;
      • 需要第三方软件支持协调机制;目前支持 Zookeeper 及 Etcd;
      • 不支持主从复制,需要另外实现;
      • Codis 采用了 Proxy 的方案,所以必然会带来单机性能的损;

二、什么是 Redis Cluster?

Redis Cluster(官方)

为了解决主从复制的瓶颈,Redis 官方发布了一种分布式集群,填补了主从架构的不足,以下是 Redis Cluster 的一些特性:

  • 官方实现;
  • 需要 Redis 3.0 或更高版本;
  • 优点:
    • 无中心的 P2P Gossip 分散式模式;
    • 更少的來回次数并降低延迟;
    • 自动于多个 Redis 节点进行分片;
    • 不需要第三方软件支持协调机制;
  • 缺点:
    • 依赖于 Redis 3.0 或更高版本;
    • 需要时间验正其稳定性;
    • 沒有后台界面;
    • 需要智能客戶端;
    • Redis 客戶端必须支持 Redis Cluster 架构;
    • 较 Codis 有更多的维护升级成本;

有中心节点 和 无中心节点 的区别

首先要了解,一个完整的数据结构是由 2 部分组成,元数据和对应的数据本身。客户端在获取数据时,会先读取元数据,再根据元数据中保存的数据的实际存放位置,去读取数据本身。

在 Redis Cluster 分布式存储系统中,元数据就是一个 key,而数据本身是一个对应的 value,而 key 就保存在 Leader 节点上,当客户端访问数据时会先从 Leader 节点读取元数据,再根据元数据去对应的数据节点读取数据本身。

而 Leader 节点在分布式系统中有两种实现方式:

  • 一种是有中心节点,即集群中只有一台 Leader,保存了整个集群的元数据;
  • 另一种是无中心节点,即每个节点都是 Leader,都保存了整个集群的元数据,并且会相互同步。客户端可以向任意一个 Leader 节点请求 key

Redis Cluster 就属于无中心节点的。

Redis Cluster 在生产环境的使用(关于智能客户端)

然而,在 Redis Cluster 中虽然每个 Leader 节点都保存着集群全部的元数据,有冗余效果,但是每个数据节点却只保存着一部分数据,如果宕机的话数据就会丢失。所以要为每个数据节点构建从节点,并且能在主节点宕机时自动把从节点提升为主节点,这就要引入 Sentinel 集群来监控主从的状态。

在这样的架构下,就要求客户端必须是一个智能客户端,能够从 Leader 节点获取到 key,然后自行去对应的数据节点读取数据,并且在集群状态发生变化后能自动找到新的节点。

Cerberus (芒果TV) 就是这样的一个客户端。

Cerberus (芒果TV) 的特性

  • 优点:
    • 数据自动平衡;
    • 本身实现了 Redis 的 Smart Client;
    • 支持读写分离;
  • 缺点:
    • 依赖 Redis 3.0 或更高版本;
    • 代理分片机制引入更多的來回次数并增大延迟;
    • 需要时间验正其稳定性;
    • 没有后台界面;

本文不展开讲解客户端,主要讲解 Redis Cluster 的原理和部署方法。

三、Redis Cluster 集群简介

Redis 面临和 MySQL 一样的问题:主从复制这种集群扩展方式,只能缓解对读请求压力的均衡,而对写请求没有任何均衡的作用。每一个节点都要写所有的数据,每一个节点都是整个集群数据集的一个副本。总之,主从这种方式能够降低读的压力,能够提供冗余的效果,但是并不能确保数据是分散的,不能对写操作带来任何压力的分散。

下面是官网对 Redis Cluster 介绍的中文翻译

Redis Cluster 是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。

Redis 集群不支持那些需要同时处理多个键的 Redis 命令,因为执行这些命令需要在多个 Redis 节点之间移动数据,并且在高负载的情况下,这些命令将降低 Redis 集群的性能,并导致不可预测的行为。

Redis 集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

Redis 集群提供了以下两个好处:

  • 将数据自动切分(split)到多个节点的能力。
  • 当集群中的一部分节点失效或者无法进行通讯时,仍然可以继续处理命令请求的能力。

Redis Cluster 实现原理

Redis 集群是两级路由,使用了数据分片(sharding)而非一致性哈希(consistency hashing)来实现:一个 Redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个键都属于这 16384 个哈希槽的其中一个,集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽,其中 CRC16(key) 语句用于计算键 keyCRC16 校验和

集群中的每个节点负责处理一部分哈希槽。举个例子,一个集群可以有三个哈希槽,其中:

  • 节点 A 负责处理 0 号至 5500 号哈希槽。
  • 节点 B 负责处理 5501 号至 11000 号哈希槽。
  • 节点 C 负责处理 11001 号至 16384 号哈希槽。

Redis 集群 不保证数据的强一致性(strong consistency):在特定条件下,Redis 集群可能会丢失已经被执行过的写命令。

slot

关于 Redis Cluster 的更多细节,建议读:

四、配置 Redis Cluster

首先,如果要构建集群,节点就不能是主从复制架构,需要删除配置文件中与主从复制相关的配置。

Redis Cluster 集群由多个运行在集群模式(cluster mode)下的 Redis 程序组成,而集群模式需要通过配置来开启,开启集群模式的 Redis 服务将可以使用集群特有的功能和命令。

官方提供了自动部署 Redis Cluster 的教程,为了更好的理解,这里采用手动创建的方式部署集群。

1. 设置配置文件,启用集群功能

以下是一个包含了最少选项的集群配置文件示例,在配置文件 /etc/redis.conf 中的 ##### REDIS CLUSTER ##### 配置段中启用:

  • port 7000:定义 Redis 监听的端口;
  • cluster-enabled yes:用于开实例的集群模式;
  • cluster-config-file nodes.conf:设定了保存节点配置文件的路径,节点配置文件无须人为修改,它由 Redis 集群在启动时创建,并在有需要时自动进行更新,配置时避免名称冲突,默认保存在 /var/lib/redis 下;
  • cluster-node-timeout 5000:集群节点互联超时的阈值,单位毫秒;
  • cluster-slave-validity-factor<factor>:每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点。如果从节点与主节点断线时间超过 cluster-node-time * cluster-slave-validity-factor,则当前从节点不具备故障转移资格。

PS:要让集群正常运作至少需要三个主节点,不过在生产环境中使用集群功能时,强烈建议使用六个节点:其中三个为主节点,而其余三个则是各个主节点的从节点。

启动 Redis 服务

1
systemctl start redis

查看监听的端口

1
2
3
4
ss -tnl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:6379 *:*
LISTEN 0 128 *:16379 *:*

每个 Redis Cluster node 需要打开两个 TCP 端口。一个 TCP 端口为 clients 提供服务,例如这个端口为 6379,那么该端口增加 10000 就是 Cluster 的 node 之间数据端口,例如 16379

端口 16379 是 Redis Cluster 的 node-to-node 使用二进制协议通信的端口。
因此,Redis Cluster 每个 node 都必须打开两个 TCP 端口,否则整个 Redis Cluster 是无法正常工作的。

使用客户端连入,输入 CLUSTER INFO 可以查看集群状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
-> # redis-cli 
127.0.0.1:6379> CLUSTER INFO
cluster_state:fail /* fail 说明集群运行是错误的,因为集群还没有配置完成 */
cluster_slots_assigned:0 /* 已分配的哈希槽数量 */
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0

2. 为每个节点分配哈希槽(hash slot)

使用 CLUSTER ADDSLOTS slot [slot ...] 为当前节点添加 slot,需要一个一个指明,比如要添加 5000 个,就需要写成 CLUSTER ADDSLOTS 1 2 3 ... 5498 5499 5500,太恐怖了。

好在 Redis 的客户端工具支持不进入交互模式而直接执行命令,所以可以在 shell 下以命令行展开的方式添加 slot,如下:

1
2
3
4
5
6
~] # redis-cli -h 192.168.50.11 -a monster -c CLUSTER ADDSLOTS {0..5500}
OK
~] # redis-cli -h 192.168.50.12 -a monster -c CLUSTER ADDSLOTS {5501..11000}
OK
~] # redis-cli -h 192.168.50.13 -a monster -c CLUSTER ADDSLOTS {11001..16383}
OK

3. 让节点之间互相发现

现在集群都配置好了,但各自都是独立在运行,最后一步在任意节点上使用 CLUSTER MEET ip port 来和其他的所有节点“认识”一下,集群就可以正常工作了。

这里使用 192.168.50.11 去“认识” 1213 就行了,1213 之间会间接认识。

1
2
127.0.0.1:6379> CLUSTER MEET 192.168.50.12 6379
127.0.0.1:6379> CLUSTER MEET 192.168.50.13 6379

之后在所有的节点上看到的都应该是如下状态:

1
2
3
~] # redis-cli -h 192.168.50.13 -a monster CLUSTER INFO | grep -E 'cluster_state|cluster_slots_assigned'
cluster_state:ok # 集群状态转为 OK
cluster_slots_assigned:16384 # slot 数量为 16384

验证

redis-cli 客户端内插入键值:

1
2
3
4
127.0.0.1:6379> SET NAME tom
OK
127.0.0.1:6379> SET ID 1
(error) MOVED 16148 192.168.50.13:6379

可以看到,在插入 NAME tom 时是正常的,但是当插入 ID 1 时,却提示说这个键经过 hash 计算并取模后应该落在第 16148 号哈希槽上,但是这个槽不在本机,而是在 192.168.50.13 这个节点。

说明了每个节点都可以帮你路由到你要的数据放在哪,但可能本节点并不是存放这个数据的地方。

此时应该去 192.168.50.13 再次插入 ID 1 就会成功。

这一系列操作需要一个智能客户端来自动实现,而 redis-cli 是不支持的。