Redis集群方案
Redis Cluster 集群模式通常具有 高可用、可扩展性、分布式、容错等特性。Redis分布式方案一般有两种
客户端分区方案
客户端 就已经决定数据会被 存储到哪个 redis 节点或者从哪个 redis节点读取数据。其主要思想是采用哈希算法将 Redis 数据的 key进行散列,通过 hash函数,特定的 key会 映射到特定的 Redis节点上。
客户端分区方案 的代表为 Redis Sharding
,Redis Sharding
是 Redis Cluster
出来之前,业界普遍使用的 Redis
多实例集群方法。
Java
的 Redis
客户端驱动库 Jedis
,支持 Redis Sharding
功能,即 ShardedJedis
以及 结合缓存池 的 ShardedJedisPool
。
优点
不使用第三方中间件,分区逻辑 可控,配置 简单,节点之间无关联,容易 线性扩展,灵活性强。
缺点
客户端 无法动态增删服务节点,客户端需要自行维护分发逻辑,客户端之间 无连接共享,会造成 连接浪费。
代理分区方案
客户端发送请求到一个代理组件,代理 解析 客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端。
优点:简化客户端的分布式逻辑,客户端透明接入,切换成本低,代理的 转发 和 存储 分离。
缺点:多了一层代理层,加重了架构部署复杂度和 性能损耗。
代理分区 主流实现的有方案有 Twemproxy
和 Codis
。
Twemproxy
Twemproxy
也叫nutcraker
,是redis
和memcache
的 中间代理服务器 程序。Twemproxy
作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个Redis
服务器,再原路返回。Twemproxy
存在 单点故障 问题,需要结合Lvs
和Keepalived
做 高可用方案。
优点
应用范围广,稳定性较高,中间代理层高可用。
缺点
无法平滑地水平扩容/缩容,无可视化管理界面,运维不友好,出现故障,不能自动转移
Codis
Codis
是一个 分布式Redis
解决方案,对于上层应用来说,连接Codis-Proxy
和直接连接 原生的Redis-Server
没有的区别。Codis
底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis
采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。
优点
实现了上层
Proxy
和底层Redis
的 高可用,数据分片 和 自动平衡,提供 命令行接口 和RESTful API
,提供 监控 和 管理 界面,可以动态 添加 和 删除Redis
节点。
缺点
部署架构和配置复杂,不支持跨机房和多租户,不支持鉴权管理。
查询路由方案
客户端随机地 请求任意一个 Redis
实例,然后由 Redis
将请求 转发 给 正确 的 Redis
节点。Redis Cluster
实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis
节点 转发 到另一个 Redis
节点,而是在 客户端 的帮助下直接 重定向( redirected
)到正确的 Redis
节点。
优点
无中心节点,数据按照 槽 存储分布在多个
Redis
实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。
缺点
严重依赖
Redis-trib
工具,缺乏 监控管理,需要依赖Smart Client
(维护连接,缓存路由表,MultiOp
和Pipeline
支持)。Failover
节点的 检测过慢,不如 中心节点ZooKeeper
及时。Gossip
消息具有一定开销。无法根据统计区分 冷热数据。
数据分布
数据分布理论
分布式数据库 首先要解决把 整个数据集 按照 分区规则 映射到 多个节点 的问题,即把 数据集 划分到 多个节点 上,每个节点负责 整体数据 的一个 子集。
数据分布通常有 哈希分区 和 顺序分区 两种方式,对比如下:
分区方式 | 特点 | 相关产品 |
---|---|---|
哈希分区 | 离散程度好,数据分布与业务无关,无法顺序访问 | Redis Cluster,Cassandra,Dynamo |
顺序分区 | 离散程度易倾斜,数据分布与业务相关,可以顺序访问 | BigTable,HBase,Hypertable |
由于 Redis Cluster
采用 哈希分区规则,这里重点讨论 哈希分区。常见的 哈希分区 规则有几种,下面分别介绍:
节点取余分区
使用特定的数据,如 Redis
的 键 或 用户 ID
,再根据 节点数量 N
使用公式:hash(key)% N
计算出 哈希值,用来决定数据 映射 到哪一个节点上。
优点
这种方式的突出优点是 简单性,常用于 数据库 的 分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为
512
或1024
张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 将 表 迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。
缺点
当 节点数量 变化时,如 扩容 或 收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移。
一致性哈希分区
一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 收尾相接 的 Hash
环上,每个 key
在计算 Hash
后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash
环上 顺时针相邻 的 后续节点。
优点
加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点
加减节点 会造成 哈希环 中部分数据 无法命中。当使用 少量节点 时,节点变化 将大范围影响 哈希环 中 数据映射,不适合 少量数据节点 的分布式方案。普通 的 一致性哈希分区 在增减节点时需要 增加一倍 或 减去一半 节点才能保证 数据 和 负载的均衡。
注意
因为一致性哈希分区的这些缺点,一些分布式系统采用虚拟槽对一致性哈希进行改进,比如
Dynamo 系统。
虚拟槽分区
虚拟槽分区 巧妙地使用了 哈希空间,使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 槽(slot
)。这个范围一般 远远大于 节点数,比如 Redis Cluster
槽范围是 0 ~ 16383
。槽 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽,如图所示:
当前集群有
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
从集群中 移除 即可。由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除 或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.
Redis的数据分区
Redis Cluster
采用 虚拟槽分区,所有的 键 根据 哈希函数 映射到 0~16383
整数槽内,计算公式:slot = CRC16(key)& 16383
。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:
Redis虚拟槽分区的特点
# 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
# 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。
# 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。
Redis集群的功能限制
Redis
集群相对 单机 在功能上存在一些限制,需要 开发人员 提前了解,在使用时做好规避。
key批量操作支持有限
类似
mset
、mget
操作,目前只支持对具有相同slot
值的key
执行 批量操作。对于 映射为不同slot
值的key
由于执行mget
、mget
等操作可能存在于多个节点上,因此不被支持。
key事务操作支持有限
只支持 多
key
在 同一节点上 的 事务操作,当多个key
分布在 不同 的节点上时 无法 使用事务功能。
key
作为数据分区的最小粒度`
不能将一个 大的键值 对象如
hash
、list
等映射到 不同的节点。
不支持多数据库空间
单机 下的
Redis
可以支持16
个数据库(db0 ~ db15
),集群模式 下只能使用 一个 数据库空间,即db0
。
复制结构只支持一层
从节点只能复制 主节点,不支持 嵌套树状复制 结构
Redis集群搭建
Redis-Cluster
是Redis
官方的一个 高可用 解决方案,Cluster
中的Redis
共有2^14(16384)
个slot
槽。创建Cluster
后,槽 会 平均分配 到每个Redis
节点上。下面介绍一下本机启动
6
个Redis
的 集群服务,并使用redis-trib.rb
创建 3主3从 的 集群。搭建集群工作需要以下三个步骤:
准备节点
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
目录下。
创建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
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 es
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
安装Ruby环境
wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.5.tar.gz
tar zxvf ruby-2.3.5.tar.gz
cd ruby-2.3.5
./configure --prefix=/opt/ruby
make && make install
ln -s /opt/ruby/bin/ruby /usr/bin/ruby
ln -s /opt/ruby/bin/gem /usr/bin/gem
[root@redis-cluster redis-cluster]# ruby -v
ruby 2.3.5p376 (2017-09-14 revision 59905) [x86_64-linux]
准备rubygem redis依赖
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
# 一般会报错以下信息
ERROR: Loading command: install (LoadError)
cannot load such file -- zlib
ERROR: While executing gem ... (NoMethodError)
undefined method `invoke_with_build_args' for nil:NilClass
# 我们做下下面步骤
yum -y install zlib-devel
cd ruby-2.3.5/ext/zlib/
ruby ./extconf.rb
make && make install
gem install -l redis-3.3.0.gem
# Successfully installed redis-3.3.0
# 显示这条信息代表安装成功
拷贝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数据导入集群 |
启动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节点的进程状态
[root@redis-cluster redis-cluster]# ps -ef |grep redis-server
root 4694 1 0 10:48 ? 00:00:01 redis-server 127.0.0.1:6380 [cluster]
root 4770 1 0 10:49 ? 00:00:01 redis-server 127.0.0.1:6379 [cluster]
root 4803 1 0 10:50 ? 00:00:01 redis-server 127.0.0.1:6381 [cluster]
root 4874 1 0 10:50 ? 00:00:01 redis-server 127.0.0.1:6389 [cluster]
root 4913 1 0 10:50 ? 00:00:01 redis-server 127.0.0.1:6390 [cluster]
root 4952 1 0 10:51 ? 00:00:01 redis-server 127.0.0.1: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
redis-trib关联集群节点
按照从主到从的方式从左到右依次排列
6个redis节点`
./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: ad7c4eb8209adff15b273f27b2e540f84a3489c1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
M: e4a47642b1eca92ecedbcfcbcfda6ad2c80b8c61 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
M: 16edddbdaed76241ebe575f997f72e0bb769e19f 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
S: 865a7074db84ba0377373aaefa31767260ed1dc7 127.0.0.1:6389
replicates e4a47642b1eca92ecedbcfcbcfda6ad2c80b8c61
S: 065d548e79b4aecdee54629921e7fd3c7443deda 127.0.0.1:6390
replicates 16edddbdaed76241ebe575f997f72e0bb769e19f
S: 4a2b0d3cffd931aec5d140549e4333b8b1455584 127.0.0.1:6391
replicates ad7c4eb8209adff15b273f27b2e540f84a3489c1
Can I set the above configuration? (type 'yes' to accept): yes
# 此时会产生一个交互,我们输入yes,redis-trib.rb开始执行节点握手和槽分配.
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
# 最后出现上面四行信息代表成功
redis集群完整性检测
使用
redis-trib.rb check
命令检测之前创建的 两个集群 是否成功,check
命令只需要给出集群中 任意一个节点地址 就可以完成 整个集群 的 检查工作,命令如下:
redis-trib.rb check 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: ad7c4eb8209adff15b273f27b2e540f84a3489c1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
S: 065d548e79b4aecdee54629921e7fd3c7443deda 127.0.0.1:6390
slots: (0 slots) slave
replicates 16edddbdaed76241ebe575f997f72e0bb769e19f
M: e4a47642b1eca92ecedbcfcbcfda6ad2c80b8c61 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
1 additional replica(s)
M: 16edddbdaed76241ebe575f997f72e0bb769e19f 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 865a7074db84ba0377373aaefa31767260ed1dc7 127.0.0.1:6389
slots: (0 slots) slave
replicates e4a47642b1eca92ecedbcfcbcfda6ad2c80b8c61
S: 4a2b0d3cffd931aec5d140549e4333b8b1455584 127.0.0.1:6391
slots: (0 slots) slave
replicates ad7c4eb8209adff15b273f27b2e540f84a3489c1
[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 个槽位。