redis cluster

优劣势

概述

Redis Cluster 是 Redis 原生的数据分片实现,可以自动在多个节点上分布数据,不需要依赖任何外部的工具。

Redis Cluster 中所有 key 会被分派到 16384 个 slot(hash 槽)中,这些 slot 又会被指派到多个 Redis 节点上。

cluster redis 实例 redis cluster keys_redis

一个 key 会映射到某个 slot,算法:

HASH_SLOT = CRC16(key) mod 16384

这个 slot 的机制会给我们带来一点麻烦,后面会讲到。

优势

  1. 高性能
  • Redis Cluster 的性能与单节点部署是同级别的。
  1. 高可用
  • Redis Cluster 支持标准的 master-replica 配置来保障高可用和高可靠。
  • Redis Cluster 也实现了一个类似 Raft 的共识方式,来保障整个集群的可用性。
  1. 易扩展
  • 向 Redis Cluster 中添加新节点,或者移除节点,都是透明的,不需要停机。
  • 水平、垂直方向都非常容易扩展。
  1. 原生
  • 部署 Redis Cluster 不需要其他的代理或者工具,而且 Redis Cluster 和单机 Redis 几乎完全兼容。

限制

  1. 需要客户端的支持
  • 客户端需要修改,以便支持 Redis Cluster。
  • 虽然 Redis Cluster 已经发布有几年时间了,但仍然有些客户端是不支持的,所以需要到 Redis 官网上去查询一下。
  1. 只支持一个数据库
  • 不像单机 Redis,Redis Cluster 只支持一个数据库(database 0),select 命令就不能用了,但实际也很少有人使用多数据库,所以这个限制并没什么影响。
  1. Multi-Key 操作受限

Multi-Key(多 key)是什么意思?某些情况是多 key 的操作,例如:

  • SUNION,这类命令会操作多个 key
  • 事务,会在一个事务中操作多个 key
  • LUA 脚本,也会操作多个 key

这类情况都需要特别注意,因为:Redis Cluster 要求,只有这些 key 都在同一个 slot 时才能执行。例如,有 2 个 key,key1 和 key2。

  • key1 是映射到 5500 这个 slot 上,存储在 Node A。
  • key2 是映射到 5501 这个 slot 上,存储在 Node B。

那么就不能对 key1 和 key2 做事务操作。

Multi-Key 限制的处理

对于多 key 场景,需要做好数据空间的设计,Redis Cluster 提供了一个 hash tag 的机制,可以让我们把一组 key 映射到同一个 slot。

例如:user1000.following 这个 key 保存用户 user1000 关注的用户;user1000.followers 保存用户 user1000 的粉丝。

这两个 key 有一个共同的部分 user1000,可以指定对这个共同的部分做 slot 映射计算,这样他们就可以在同一个槽中了。

使用方式:

{user1000}.following 和 {user1000}.followers

就是把共同的部分使用 { } 包起来,计算 slot 值时,如果发现了花括号,就会只对其中的部分进行计算。

小结

Multi-Key 这一点是 Redis Cluster 对于我们日常使用中最大的限制,一定要注意,如果多 key 不在同一个 slot 中就会报错,例如:

(error) CROSSSLOT Keys in request don't hash to the same slot

需要使用 hash tag 设计好 key 的空间。

redis cluster搭建

准备节点

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

注意:建议为集群内 所有节点 统一目录,一般划分三个目录:confdatalog,分别存放 配置数据日志 相关文件。把 6 个节点配置统一放在 conf 目录下。

创建redis各实例目录
$ sudo mkdir -p /home/ubuntu/code/redis/cluster
$ cd /home/ubuntu/code/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 /home/ubuntu/code/redis/cluster/data/redis-${port}
# 进程文件
pidfile /home/ubuntu/code/redis/cluster/redis-${port}.pid
# 日志文件
logfile /home/ubuntu/code/redis/cluster/log/redis-${port}.log
# 端口号
port ${port}
# 开启集群模式,把注释#去掉
cluster-enabled yes
# 集群的配置,配置文件首次启动自动生成
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-${port}.conf
# 请求超时,设置10秒
cluster-node-timeout 10000
# aof日志开启,有需要就开启,它会每次写操作都记录一条日志
appendonly yes
  • redis-6379.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6379
pidfile /home/ubuntu/code/redis/cluster/redis-6379.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6379.log
port 6379
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6379.conf
cluster-node-timeout 10000
appendonly yes
  • redis-6389.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6389
pidfile /home/ubuntu/code/redis/cluster/redis-6389.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6389.log
port 6389
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6389.conf
cluster-node-timeout 10000
appendonly yes
  • redis-6380.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6380
pidfile /home/ubuntu/code/redis/cluster/redis-6380.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6380.log
port 6380
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6380.conf
cluster-node-timeout 10000
appendonly yes
  • redis-6390.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6390
pidfile /home/ubuntu/code/redis/cluster/redis-6390.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6390.log
port 6390
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6390.conf
cluster-node-timeout 10000
appendonly yes
  • redis-6381.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6381
pidfile /home/ubuntu/code/redis/cluster/redis-6381.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6381.log
port 6381
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6381.conf
cluster-node-timeout 10000
appendonly yes
  • redis-6391.conf
daemonize yes
bind 127.0.0.1
dir /home/ubuntu/code/redis/cluster/data/redis-6391
pidfile /home/ubuntu/code/redis/cluster/redis-6391.pid
logfile /home/ubuntu/code/redis/cluster/log/redis-6391.log
port 6391
cluster-enabled yes
cluster-config-file /home/ubuntu/code/redis/cluster/conf/node-6391.conf
cluster-node-timeout 10000
appendonly yes

环境准备

安装Ruby环境
sudo apt install ruby
准备 rubygem redis 依赖
### 更换国内源
gem sources --add http://gems.ruby-china.org/  --remove https://rubygems.org/
### 如果证书有问题
vi ~/.gemrc
添加
:ssl_verify_mode: 0

sudo gem install redis
拷贝redis-trib.rb到集群根目录

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

sudo cp /usr/local/redis-4.0.11/src/redis-trib.rb /home/ubuntu/code/redis/cluster/

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

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ ./redis-trib.rb
Usage: redis-trib <command> <options> <arguments ...>

  create          host1:port1 ... hostN:portN
                  --replicas <arg>
  check           host:port
  info            host:port
  fix             host:port
                  --timeout <arg>
  reshard         host:port
                  --from <arg>
                  --to <arg>
                  --slots <arg>
                  --yes
                  --timeout <arg>
                  --pipeline <arg>
  rebalance       host:port
                  --weight <arg>
                  --auto-weights
                  --use-empty-masters
                  --timeout <arg>
                  --simulate
                  --pipeline <arg>
                  --threshold <arg>
  add-node        new_host:new_port existing_host:existing_port
                  --slave
                  --master-id <arg>
  del-node        host:port node_id
  set-timeout     host:port milliseconds
  call            host:port command arg arg .. arg
  import          host:port
                  --from <arg>
                  --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.rbredis 作者用 ruby 完成的。redis-trib.rb 命令行工具 的具体功能如下:

命令

作用

create

创建集群

check

检查集群

info

查看集群信息

fix

修复集群

reshard

在线迁移slot

rebalance

平衡集群节点slot数量

add-node

将新节点加入集群

del-node

从集群中删除节点

set-timeout

设置集群节点间心跳连接的超时时间

call

在集群全部节点上执行命令

import

将外部redis数据导入集群

安装集群

启动redis服务节点

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

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 节点的进程状态:

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ ps -ef | grep redis
root     14540     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6379 [cluster]
root     14559     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6389 [cluster]
root     14568     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6380 [cluster]
root     14579     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6390 [cluster]
root     14591     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6381 [cluster]
root     14601     1  0 23:03 ?        00:00:00 redis-server 127.0.0.1:6391 [cluster]
ubuntu   14614 19808  0 23:03 pts/0    00:00:00 grep --color=auto redis

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

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ tree -L 3 .
.
├── 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
│   │   └── appendonly.aof
│   ├── redis-6380
│   │   └── appendonly.aof
│   ├── redis-6381
│   │   └── appendonly.aof
│   ├── redis-6389
│   │   └── appendonly.aof
│   ├── redis-6390
│   │   └── appendonly.aof
│   └── redis-6391
│       └── appendonly.aof
├── log
│   ├── redis-6379.log
│   ├── redis-6380.log
│   ├── redis-6381.log
│   ├── redis-6389.log
│   ├── redis-6390.log
│   └── redis-6391.log
├── redis-6379.pid
├── redis-6380.pid
├── redis-6381.pid
├── redis-6389.pid
├── redis-6390.pid
├── redis-6391.pid
└── redis-trib.rb

9 directories, 31 files
redis-trib 关联集群节点

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

$ 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-6379redis-6380redis-6381。然后将各个 从节点 指向 主节点,进行 数据同步

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ 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
>>> 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: af1b083f7535f5a4e38f39ce866a63ea59acd77d 127.0.0.1:6379
   slots:0-5460 (5461 slots) master
M: 885484956fad214f7ef924abbb4f3401f092dee2 127.0.0.1:6380
   slots:5461-10922 (5462 slots) master
M: b35790734c8c74596662dab2edbb4b38f7ba5f9a 127.0.0.1:6381
   slots:10923-16383 (5461 slots) master
S: 506e5b955a614f01b37be261e0bb02063d3e93d7 127.0.0.1:6389
   replicates af1b083f7535f5a4e38f39ce866a63ea59acd77d
S: e741efde789f1037ceb7609518ae0d94c929d97d 127.0.0.1:6390
   replicates 885484956fad214f7ef924abbb4f3401f092dee2
S: cb3129610af155ed4d22c258b769edd5fd90a526 127.0.0.1:6391
   replicates b35790734c8c74596662dab2edbb4b38f7ba5f9a
Can I set the above configuration? (type 'yes' to accept): yes

然后输入 yesredis-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: af1b083f7535f5a4e38f39ce866a63ea59acd77d 127.0.0.1:6379
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: 885484956fad214f7ef924abbb4f3401f092dee2 127.0.0.1:6380
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: e741efde789f1037ceb7609518ae0d94c929d97d 127.0.0.1:6390
   slots: (0 slots) slave
   replicates 885484956fad214f7ef924abbb4f3401f092dee2
S: 506e5b955a614f01b37be261e0bb02063d3e93d7 127.0.0.1:6389
   slots: (0 slots) slave
   replicates af1b083f7535f5a4e38f39ce866a63ea59acd77d
S: cb3129610af155ed4d22c258b769edd5fd90a526 127.0.0.1:6391
   slots: (0 slots) slave
   replicates b35790734c8c74596662dab2edbb4b38f7ba5f9a
M: b35790734c8c74596662dab2edbb4b38f7ba5f9a 127.0.0.1:6381
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

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

redis主节点的日志

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

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ cat log/redis-6379.log
14539:C 10 May 23:03:09.319 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
14539:C 10 May 23:03:09.320 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=14539, just started
14539:C 10 May 23:03:09.320 # Configuration loaded
14540:M 10 May 23:03:09.322 * No cluster configuration found, I'm af1b083f7535f5a4e38f39ce866a63ea59acd77d
14540:M 10 May 23:03:09.347 * Running mode=cluster, port=6379.
14540:M 10 May 23:03:09.347 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
14540:M 10 May 23:03:09.347 # Server initialized
14540:M 10 May 23:03:09.347 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
14540:M 10 May 23:03:09.347 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
14540:M 10 May 23:03:09.347 * Ready to accept connections
14540:M 10 May 23:05:54.617 # configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH
14540:M 10 May 23:05:54.649 # IP address for this node updated to 127.0.0.1
14540:M 10 May 23:05:59.587 * Slave 127.0.0.1:6389 asks for synchronization
14540:M 10 May 23:05:59.587 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for '01c59b52d4957d209d9ed4e45a1a7240c8f04b15', my replication IDs are '25df155ecfb80e0d3329c5bc65eb9c933a0c8692' and '0000000000000000000000000000000000000000')
14540:M 10 May 23:05:59.587 * Starting BGSAVE for SYNC with target: disk
14540:M 10 May 23:05:59.587 * Background saving started by pid 14975
14975:C 10 May 23:05:59.591 * DB saved on disk
14975:C 10 May 23:05:59.592 * RDB: 0 MB of memory used by copy-on-write
14540:M 10 May 23:05:59.595 * Background saving terminated with success
14540:M 10 May 23:05:59.595 # Cluster state changed: ok
14540:M 10 May 23:05:59.595 * Synchronization with slave 127.0.0.1:6389 succeeded
redis集群完整性检测

使用 redis-trib.rb check 命令检测之前创建的 两个集群 是否成功,check 命令只需要给出集群中 任意一个节点地址 就可以完成 整个集群检查工作,命令如下:

$ ./redis-trib.rb check 127.0.0.1:6379

当最后输出如下信息,提示集群 所有的槽 都已分配到节点:

ubuntu@VM-154-193-ubuntu:~/code/redis/cluster$ ./redis-trib.rb check 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: af1b083f7535f5a4e38f39ce866a63ea59acd77d 127.0.0.1:6379
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: 885484956fad214f7ef924abbb4f3401f092dee2 127.0.0.1:6380
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: e741efde789f1037ceb7609518ae0d94c929d97d 127.0.0.1:6390
   slots: (0 slots) slave
   replicates 885484956fad214f7ef924abbb4f3401f092dee2
S: 506e5b955a614f01b37be261e0bb02063d3e93d7 127.0.0.1:6389
   slots: (0 slots) slave
   replicates af1b083f7535f5a4e38f39ce866a63ea59acd77d
S: cb3129610af155ed4d22c258b769edd5fd90a526 127.0.0.1:6391
   slots: (0 slots) slave
   replicates b35790734c8c74596662dab2edbb4b38f7ba5f9a
M: b35790734c8c74596662dab2edbb4b38f7ba5f9a 127.0.0.1:6381
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

小结

本文介绍了 Redis 集群解决方案数据分布集群搭建。集群方案包括 客户端分区 方案,代理分区 方案 和 查询路由 方案。数据分布 部分简单地对 节点取余 分区,一致性哈希 分区以及 虚拟槽 分区进行了阐述和对比。最后对使用 Redis-trib 搭建了一个 三主三从虚拟槽 集群示例。