Redis集群模式搭建与原理详解

1. Redis集群方案

Redis Cluster 集群模式通常具有高可用、可扩展性、分布式、容错等特性。Redis 分布式方案一般有两种:

1.1 客户端分区方案

客户端就已经决定数据会被存储到哪个 redis 节点或者从哪个 redis 节点读取数据。其主要思想是采用哈希算法将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key会映射到特定的 Redis 节点上。

深入剖析Redis-Redis集群模式搭建与原理详解

客户端分区方案的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis多实例集群方法。Java 的 Redis 客户端驱动库 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPool。

优点

不使用第三方中间件,分区逻辑可控,配置简单,节点之间无关联,容易线性扩展,灵活性强。

缺点

客户端无法动态增删服务节点,客户端需要自行维护分发逻辑,客户端之间无连接共享,会造成连接浪费。

1.2. 代理分区方案

客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端。

优点:简化客户端的分布式逻辑,客户端透明接入,切换成本低,代理的转发和存储分离。 缺点:多了一层代理层,加重了架构部署复杂度和性能损耗。

深入剖析Redis-Redis集群模式搭建与原理详解

代理分区主流实现的有方案有 Twemproxy 和 Codis。

1.2.1. Twemproxy

Twemproxy 也叫 nutcraker,是 twitter 开源的一个 redis 和 memcache 的中间代理服务器程序。Twemproxy 作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在单点故障问题,需要结合 Lvs 和 Keepalived 做高可用方案。

深入剖析Redis-Redis集群模式搭建与原理详解

优点:应用范围广,稳定性较高,中间代理层高可用。 缺点:无法平滑地水平扩容/缩容,无可视化管理界面,运维不友好,出现故障,不能自动转移。

1.2.2. Codis

Codis 是一个分布式Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接原生的Redis-Server 没有的区别。Codis 底层会处理请求的转发,不停机的进行数据迁移等工作。Codis 采用了无状态的代理层,对于客户端来说,一切都是透明的。

深入剖析Redis-Redis集群模式搭建与原理详解

优点

实现了上层 Proxy 和底层 Redis 的高可用,数据分片和自动平衡,提供命令行接口和 RESTful API,提供监控和管理界面,可以动态添加和删除Redis 节点。

缺点

部署架构和配置复杂,不支持跨机房和多租户,不支持鉴权管理。

1.3. 查询路由方案

客户端随机地请求任意一个 Redis 实例,然后由 Redis 将请求转发给正确的 Redis 节点。Redis Cluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个 Redis 节点转发到另一个 Redis 节点,而是在客户端的帮助下直接重定向( redirected)到正确的 Redis 节点。

深入剖析Redis-Redis集群模式搭建与原理详解

优点

无中心节点,数据按照槽存储分布在多个 Redis 实例上,可以平滑的进行节点扩容/缩容,支持高可用和自动故障转移,运维成本低。

缺点

严重依赖 Redis-trib 工具,缺乏监控管理,需要依赖 Smart Client (维护连接,缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的检测过慢,不如中心节点ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分冷热数据。

2. 数据分布

2.1. 数据分布理论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。

深入剖析Redis-Redis集群模式搭建与原理详解

数据分布通常有哈希分区和顺序分区两种方式,对比如下:

分区方式 特点 相关产品 哈希分区 离散程度好,数据分布与业务无关,无法顺序访问 Redis Cluster,Cassandra,Dynamo 顺序分区 离散程度易倾斜,数据分布与业务相关,可以顺序访问 BigTable,HBase,Hypertable 由于 Redis Cluster 采用哈希分区规则,这里重点讨论哈希分区。常见的哈希分区规则有几种,下面分别介绍:

2.1.1. 节点取余分区

使用特定的数据,如 Redis 的键或用户ID,再根据节点数量N 使用公式:hash(key)% N 计算出哈希值,用来决定数据映射到哪一个节点上。

深入剖析Redis-Redis集群模式搭建与原理详解

优点

这种方式的突出优点是简单性,常用于数据库的分库分表规则。一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的数据容量,再根据负载情况将表迁移到其他数据库中。扩容时通常采用翻倍扩容,避免数据映射全部被打乱,导致全量迁移的情况。

缺点

当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。

2.1.2. 一致性哈希分区

一致性哈希可以很好的解决稳定性问题,可以将所有的存储节点排列在收尾相接的 Hash 环上,每个 key 在计算 Hash 后会顺时针找到临接的存储节点存放。而当有节点加入或退出时,仅影响该节点在 Hash 环上顺时针相邻的后续节点。

深入剖析Redis-Redis集群模式搭建与原理详解

优点

加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

缺点

加减节点会造成哈希环中部分数据无法命中。当使用少量节点时,节点变化将大范围影响哈希环中数据映射,不适合少量数据节点的分布式方案。普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。

注意:因为一致性哈希分区的这些缺点,一些分布式系统采用虚拟槽对一致性哈希进行改进,比如 Dynamo 系统。

2.1.3. 虚拟槽分区

虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,如图所示:

深入剖析Redis-Redis集群模式搭建与原理详解

当前集群有 5 个节点,每个节点平均大约负责 3276 个槽。由于采用高质量的哈希算法,每个槽所映射的数据通常比较均匀,将数据平均划分到 5 个节点进行数据分区。Redis Cluster 就是采用虚拟槽分区。

节点1: 包含 0 到 3276 号哈希槽。 节点2:包含 3277 到 6553 号哈希槽。 节点3:包含 6554 到 9830 号哈希槽。 节点4:包含 9831 到 13107 号哈希槽。 节点5:包含 13108 到 16383 号哈希槽。

这种结构很容易添加或者删除节点。如果增加一个节点 6,就需要从节点 1 ~ 5 获得部分槽分配到节点 6 上。如果想移除节点 1,需要将节点 1 中的槽移到节点 2 ~ 5 上,然后将没有任何槽的节点 1 从集群中移除即可。

由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

2.2. Redis的数据分区

Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的键值数据,如图所示:

深入剖析Redis-Redis集群模式搭建与原理详解

2.2.1. Redis虚拟槽分区的特点

解耦数据和节点之间的关系,简化了节点扩容和收缩难度。 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。

2.3. Redis集群的功能限制

Redis 集群相对单机在功能上存在一些限制,需要开发人员提前了解,在使用时做好规避。

key批量操作支持有限。

类似 mset、mget 操作,目前只支持对具有相同 slot 值的 key 执行批量操作。对于映射为不同slot 值的 key 由于执行 mget、mget 等操作可能存在于多个节点上,因此不被支持。

key事务操作支持有限。

只支持多key 在同一节点上的事务操作,当多个 key 分布在不同的节点上时无法使用事务功能。

key 作为数据分区的最小粒度

不能将一个大的键值对象如 hash、list 等映射到不同的节点。

不支持多数据库空间

单机下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式下只能使用一个数据库空间,即 db0。

复制结构只支持一层

从节点只能复制主节点,不支持嵌套树状复制结构。

3. Redis集群搭建

Redis-Cluster 是 Redis 官方的一个高可用解决方案,Cluster 中的 Redis 共有 2^14(16384) 个 slot槽。创建 Cluster 后,槽会平均分配到每个 Redis 节点上。

下面介绍一下本机启动 6 个 Redis 的集群服务,并使用 redis-trib.rb 创建3主3从的集群。搭建集群工作需要以下三个步骤:

3.1. 准备节点

Redis 集群一般由多个节点组成,节点数量至少为 6 个,才能保证组成完整高可用的集群。每个节点需要开启配置cluster-enabled yes,让 Redis 运行在集群模式下。

Redis 集群的节点规划如下:

节点名称 端口号 是主是从 所属主节点 redis-6379 6379 主节点 — redis-6389 6389 从节点 redis-6379 redis-6380 6380 主节点 — redis-6390 6390 从节点 redis-6380 redis-6381 6381 主节点 — redis-6391 6391 从节点 redis-6381注意:建议为集群内所有节点统一目录,一般划分三个目录:conf、data、log,分别存放配置、数据和日志相关文件。把 6 个节点配置统一放在 conf 目录下。

3.1.1. 创建redis各实例目录

$ sudo mkdir -p /usr/local/redis-cluster

$ cd /usr/local/redis-cluster

$ sudo mkdir conf data log

$ sudo mkdir -p data/redis-6379 data/redis-6389 data/redis-6380 data/redis-6390 data/redis-6381 data/redis-6391

复制代码

3.1.2. redis配置文件管理

根据以下模板配置各个实例的 redis.conf,以下只是搭建集群需要的基本配置,可能需要根据实际情况做修改。

# redis后台运行

daemonize yes

# 绑定的主机端口

bind 127.0.0.1

# 数据存放目录

dir /usr/local/redis-cluster/data/redis-6379

# 进程文件

pidfile /var/run/redis-cluster/${自定义}.pid

# 日志文件

logfile /usr/local/redis-cluster/log/${自定义}.log

# 端口号

port 6379

# 开启集群模式,把注释#去掉

cluster-enabled yes

# 集群的配置,配置文件首次启动自动生成

cluster-config-file /usr/local/redis-cluster/conf/${自定义}.conf

# 请求超时,设置10秒

cluster-node-timeout 10000

# aof日志开启,有需要就开启,它会每次写操作都记录一条日志

appendonly yes

复制代码

redis-6379.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6379

pidfile /var/run/redis-cluster/redis-6379.pid

logfile /usr/local/redis-cluster/log/redis-6379.log

port 6379

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6379.conf

cluster-node-timeout 10000

appendonly yes

复制代码

redis-6389.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6389

pidfile /var/run/redis-cluster/redis-6389.pid

logfile /usr/local/redis-cluster/log/redis-6389.log

port 6389

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6389.conf

cluster-node-timeout 10000

appendonly yes

复制代码

redis-6380.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6380

pidfile /var/run/redis-cluster/redis-6380.pid

logfile /usr/local/redis-cluster/log/redis-6380.log

port 6380

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6380.conf

cluster-node-timeout 10000

appendonly yes

复制代码

redis-6390.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6390

pidfile /var/run/redis-cluster/redis-6390.pid

logfile /usr/local/redis-cluster/log/redis-6390.log

port 6390

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6390.conf

cluster-node-timeout 10000

appendonly yes

复制代码

redis-6381.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6381

pidfile /var/run/redis-cluster/redis-6381.pid

logfile /usr/local/redis-cluster/log/redis-6381.log

port 6381

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6381.conf

cluster-node-timeout 10000

appendonly yes

复制代码

redis-6391.conf

daemonize yes

bind 127.0.0.1

dir /usr/local/redis-cluster/data/redis-6391

pidfile /var/run/redis-cluster/redis-6391.pid

logfile /usr/local/redis-cluster/log/redis-6391.log

port 6391

cluster-enabled yes

cluster-config-file /usr/local/redis-cluster/conf/node-6391.conf

cluster-node-timeout 10000

appendonly yes

复制代码

3.2. 环境准备

3.2.1. 安装Ruby环境

$ sudo brew install ruby

复制代码

3.2.2. 准备rubygem redis依赖

$ sudo gem install redis

Password:

Fetching: redis-4.0.2.gem (100%)

Successfully installed redis-4.0.2

Parsing documentation for redis-4.0.2

Installing ri documentation for redis-4.0.2

Done installing documentation for redis after 1 seconds

1 gem installed

复制代码

3.2.3. 拷贝redis-trib.rb到集群根目录

redis-trib.rb 是 redis 官方推出的管理 redis集群的工具,集成在 redis 的源码 src 目录下,将基于 redis 提供的集群命令封装成简单、便捷、实用的操作工具。

$ sudo cp /usr/local/redis-4.0.11/src/redis-trib.rb /usr/local/redis-cluster

复制代码

查看 redis-trib.rb 命令环境是否正确,输出如下:

$ ./redis-trib.rb

Usage: redis-trib

create host1:port1 … hostN:portN –replicas check host:port info host:port fix host:port –timeout reshard host:port –from –to –slots –yes –timeout –pipeline rebalance host:port –weight –auto-weights –use-empty-masters –timeout –simulate –pipeline –threshold add-node new_host:new_port existing_host:existing_port –slave –master-id del-node host:port node_id set-timeout host:port milliseconds call host:port command arg arg .. arg import host:port –from –copy –replace help (show this help) For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster. 复制代码

redis-trib.rb 是 redis 作者用 ruby 完成的。redis-trib.rb命令行工具的具体功能如下:

命令 作用 create 创建集群 check 检查集群 info 查看集群信息 fix 修复集群 reshard 在线迁移slot rebalance 平衡集群节点slot数量 add-node 将新节点加入集群 del-node 从集群中删除节点 set-timeout 设置集群节点间心跳连接的超时时间 call 在集群全部节点上执行命令 import 将外部redis数据导入集群 3.3. 安装集群

3.3.1. 启动redis服务节点

运行如下命令启动 6 台 redis 节点:

sudo redis-server conf/redis-6379.conf

sudo redis-server conf/redis-6389.conf

sudo redis-server conf/redis-6380.conf

sudo redis-server conf/redis-6390.conf

sudo redis-server conf/redis-6381.conf

sudo redis-server conf/redis-6391.conf

复制代码

启动完成后,redis 以集群模式启动,查看各个 redis 节点的进程状态:

$ ps -ef | grep redis-server

0 1908 1 0 4:59下午 0:00.01 redis-server *:6379 [cluster]

0 1911 1 0 4:59下午 0:00.01 redis-server *:6389 [cluster]

0 1914 1 0 4:59下午 0:00.01 redis-server *:6380 [cluster]

0 1917 1 0 4:59下午 0:00.01 redis-server *:6390 [cluster]

0 1920 1 0 4:59下午 0:00.01 redis-server *:6381 [cluster]

0 1923 1 0 4:59下午 0:00.01 redis-server *:6391 [cluster]

复制代码

在每个 redis 节点的 redis.conf 文件中,我们都配置了 cluster-config-file 的文件路径,集群启动时,conf 目录会新生成集群节点配置文件。查看文件列表如下:

$ tree -L 3 .

.

├── appendonly.aof

├── conf

│ ├── node-6379.conf

│ ├── node-6380.conf

│ ├── node-6381.conf

│ ├── node-6389.conf

│ ├── node-6390.conf

│ ├── node-6391.conf

│ ├── redis-6379.conf

│ ├── redis-6380.conf

│ ├── redis-6381.conf

│ ├── redis-6389.conf

│ ├── redis-6390.conf

│ └── redis-6391.conf

├── data

│ ├── redis-6379

│ ├── redis-6380

│ ├── redis-6381

│ ├── redis-6389

│ ├── redis-6390

│ └── redis-6391

├── log

│ ├── redis-6379.log

│ ├── redis-6380.log

│ ├── redis-6381.log

│ ├── redis-6389.log

│ ├── redis-6390.log

│ └── redis-6391.log

└── redis-trib.rb

9 directories, 20 files

复制代码

3.3.2. redis-trib关联集群节点

按照从主到从的方式从左到右依次排列 6 个 redis 节点。

$ sudo ./redis-trib.rb create –replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391

复制代码

集群创建后,redis-trib 会先将 16384 个哈希槽分配到 3 个主节点,即 redis-6379,redis-6380 和 redis-6381。然后将各个从节点指向主节点,进行数据同步。

>>> Creating cluster

>>> Performing hash slots allocation on 6 nodes…

Using 3 masters:

127.0.0.1:6379

127.0.0.1:6380

127.0.0.1:6381

Adding replica 127.0.0.1:6390 to 127.0.0.1:6379

Adding replica 127.0.0.1:6391 to 127.0.0.1:6380

Adding replica 127.0.0.1:6389 to 127.0.0.1:6381

>>> Trying to optimize slaves allocation for anti-affinity

[WARNING] Some slaves are in the same host as their master

M: ad4b9ffceba062492ed67ab336657426f55874b7 127.0.0.1:6379

slots:0-5460 (5461 slots) master

M: df23c6cad0654ba83f0422e352a81ecee822702e 127.0.0.1:6380

slots:5461-10922 (5462 slots) master

M: ab9da92d37125f24fe60f1f33688b4f8644612ee 127.0.0.1:6381

slots:10923-16383 (5461 slots) master

S: 25cfa11a2b4666021da5380ff332b80dbda97208 127.0.0.1:6389

replicates ad4b9ffceba062492ed67ab336657426f55874b7

S: 48e0a4b539867e01c66172415d94d748933be173 127.0.0.1:6390

replicates df23c6cad0654ba83f0422e352a81ecee822702e

S: d881142a8307f89ba51835734b27cb309a0fe855 127.0.0.1:6391

replicates ab9da92d37125f24fe60f1f33688b4f8644612ee

复制代码

然后输入 yes,redis-trib.rb 开始执行节点握手和槽分配操作,输出如下:

Can I set the above configuration (type ‘yes’ to accept): yes

>>> Nodes configuration updated

>>> Assign a different config epoch to each node

>>> Sending CLUSTER MEET messages to join the cluster

Waiting for the cluster to join….

>>> Performing Cluster Check (using node 127.0.0.1:6379)

M: ad4b9ffceba062492ed67ab336657426f55874b7 127.0.0.1:6379

slots:0-5460 (5461 slots) master

1 additional replica(s)

M: ab9da92d37125f24fe60f1f33688b4f8644612ee 127.0.0.1:6381

slots:10923-16383 (5461 slots) master

1 additional replica(s)

S: 48e0a4b539867e01c66172415d94d748933be173 127.0.0.1:6390

slots: (0 slots) slave

replicates df23c6cad0654ba83f0422e352a81ecee822702e

S: d881142a8307f89ba51835734b27cb309a0fe855 127.0.0.1:6391

slots: (0 slots) slave

replicates ab9da92d37125f24fe60f1f33688b4f8644612ee

M: df23c6cad0654ba83f0422e352a81ecee822702e 127.0.0.1:6380

slots:5461-10922 (5462 slots) master

1 additional replica(s)

S: 25cfa11a2b4666021da5380ff332b80dbda97208 127.0.0.1:6389

slots: (0 slots) slave

replicates ad4b9ffceba062492ed67ab336657426f55874b7

[OK] All nodes agree about slots configuration.

>>> Check for open slots…

>>> Check slots coverage…

[OK] All 16384 slots covered.

复制代码

执行集群检查,检查各个 redis 节点占用的哈希槽(slot)的个数以及 slot覆盖率。16384 个槽位中,主节点redis-6379、redis-6380 和 redis-6381 分别占用了 5461、5461 和 5462 个槽位。

3.3.3. redis主节点的日志

可以发现,通过 BGSAVE 命令,从节点redis-6389 在后台异步地从主节点redis-6379 同步数据。

$ cat log/redis-6379.log

1907:C 05 Sep 16:59:52.960 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo

1907:C 05 Sep 16:59:52.961 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=1907, just started

1907:C 05 Sep 16:59:52.961 # Configuration loaded

1908:M 05 Sep 16:59:52.964 * Increased maximum number of open files to 10032 (it was originally set to 256).

1908:M 05 Sep 16:59:52.965 * No cluster configuration found, I’m ad4b9ffceba062492ed67ab336657426f55874b7

1908:M 05 Sep 16:59:52.967 * Running mode=cluster, port=6379.

1908:M 05 Sep 16:59:52.967 # Server initialized

1908:M 05 Sep 16:59:52.967 * Ready to accept connections

1908:M 05 Sep 17:01:17.782 # configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH

1908:M 05 Sep 17:01:17.812 # IP address for this node updated to 127.0.0.1

1908:M 05 Sep 17:01:22.740 # Cluster state changed: ok

1908:M 05 Sep 17:01:23.681 * Slave 127.0.0.1:6389 asks for synchronization

1908:M 05 Sep 17:01:23.681 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for ‘4c5afe96cac51cde56039f96383ea7217ef2af41’, my replication IDs are ‘037b661bf48c80c577d1fa937ba55367a3692921’ and ‘0000000000000000000000000000000000000000’)

1908:M 05 Sep 17:01:23.681 * Starting BGSAVE for SYNC with target: disk

1908:M 05 Sep 17:01:23.682 * Background saving started by pid 1952

1952:C 05 Sep 17:01:23.683 * DB saved on disk

1908:M 05 Sep 17:01:23.749 * Background saving terminated with success

1908:M 05 Sep 17:01:23.752 * Synchronization with slave 127.0.0.1:6389 succeeded

复制代码

参考

《Redis 开发与运维》

另外送福利了,关于Redis的学习脑图

原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/194320.html

(0)
上一篇 2021年11月16日
下一篇 2021年11月16日

相关推荐

发表回复

登录后才能评论