1. Redis Cluster介绍
Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。
1.1 数据分布理论
分布式数据库首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集。常见的分区规则有哈希分区和顺序分区。Redis Cluster采用哈希分区规则,因此接下来会讨论哈希分区规则。常见的哈希分区有以下几种:
- 节点取余分区
- 一致性哈希分区
- 虚拟槽分区
Redis Cluster采用虚拟槽分区,因此先介绍一下虚拟槽分区。
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot)。比如Redis Cluster槽的范围是0 ~ 16383。槽是集群内数据管理和迁移的基本单位。采用大范围的槽的主要目的是为了方便数据的拆分和集群的扩展,每个节点负责一定数量的槽。
1.2 Redis 数据分区
Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0 ~ 16383,计算公式:slot = CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。
下图展现一个五个节点构成的集群,每个节点平均大约负责3276个槽,以及通过计算公式映射到对应节点的对应槽的过程。
image.pngRedis虚拟槽分区的特定:
- 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
- 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。
- 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。
1.3 Redis 集群功能限制
Redis集群相对单机在功能上有一定限制。
- key批量操作支持有限。如:MSET``MGET,目前只支持具有相同slot值的key执行批量操作。
- key事务操作支持有限。支持多key在同一节点上的事务操作,不支持分布在多个节点的事务功能。
- key作为数据分区的最小粒度,因此不能将一个大的键值对象映射到不同的节点。如:hash、list。
- 不支持多数据库空间。单机下Redis支持16个数据库,集群模式下只能使用一个数据库空间,即db 0。
- 复制结构只支持一层,不支持嵌套树状复制结构。
2. docker容器方式搭建 Redis Cluster
搭建集群工作分为三步:
- 打包生成redis的镜像
- 创建Redis容器
- 节点握手
- 分配槽
2.1 打包生成redis的镜像
Redis 集群一般由多个节点组成,用docker运行即为多个容器组成。节点或者容器数量为6个才能保证组成完整高可用的集群。下面打包redis的镜像的Dockerfile文件:
# Redis
# Version 3.2.6
FROM redis:3.2.6
# MAINTAINER_INFO
MAINTAINER liano 837448792@qq.com
RUN mkdir -p /config
RUN mkdir -p /log
ENV REDIS_CONFIG /config
ENV REDIS_BIN /bin
ADD redis.conf $REDIS_CONFIG
RUN cp /usr/local/bin/redis-server $REDIS_BIN
WORKDIR /data
VOLUME ["/log","/data"]
EXPOSE 8000
ENTRYPOINT ["/bin/redis-server", "/config/redis.conf"]
这里使用了8000端口,暂且没有使用6379端口,这个可以自行指定。其中用到的redis.conf定义如下:
#设置为守护进程
daemonize no
#Redis运行的进程pid文件
pidfile /log/redis-8000.pid
#Redis服务端口号
port 8000
#Redis服务绑定ip
#bind 192.168.100.144
bind 0.0.0.0
#最大内存
maxmemory 8gb
#开启集群模式
cluster-enabled yes
#节点配置文件
cluster-config-file nodes-8000.conf
#集群节点超时时间(单位:毫秒)
cluster-node-timeout 15000
#集群是否需要所有的slot都分配给在线节点,才能正常访问
cluster-require-full-coverage no
#工作目录(aof、rdb、日志文件)
dir /data
#tcp-backlog
tcp-backlog 511
#客户端闲置多少秒后关闭连接(单位:秒)
timeout 300
#检测TCP连接活性的周期(单位:秒)
tcp-keepalive 60
#日志级别
loglevel verbose
#日志记录目录
logfile "/log/redis-8000.log"
#可用的数据库数
databases 16
#RDB保存条件
save 900 1
save 300 10
save 60 10000
#bgsave执行错误,是否停止Redis接受请求
stop-writes-on-bgsave-error no
#RDB文件是否压缩
rdbcompression yes
#RDB文件是否使用校验和
rdbchecksum yes
#RDB文件名
dbfilename dump-8000.rdb
#当从节点与主节点连接中断时,如果此参数值设置为“yes”,从节点可以继续处理客户端的
#请求。否则除info和slaveof命令之外,拒绝的所有请求并统一回复"SYNC with master in #progress"
slave-serve-stale-data yes
#从节点是否开启只读模式,集群架构下从节点默认读写都不可用,需要调用readyonly命令#开启只读模式
slave-read-only yes
#是否开启无盘复制
repl-diskless-sync no
#开启无盘复制后,需要延迟多少秒后进行创建RDB操作,一般用于同时加入多个从节点时,#保证多个从节点可共享RDB
repl-diskless-sync-delay 5
#是否开启主从复制socket的NO_DELAY选项:yes:Redis会合并小的TCP包来节省带宽,但##是这样增加同步延迟,造成主#从数据不一致;no:主节点会立即发送同步数据,没有延迟
repl-disable-tcp-nodelay no
#从节点的优先级
slave-priority 100
#是否开启AOF持久化模式
appendonly no
#Lua脚本“超时时间”(单位:毫秒)
lua-time-limit 5000
#慢查询被记录的阀值(单位微秒)
slowlog-log-slower-than 10000
#最多记录慢查询的条数
slowlog-max-len 1000
#Redis服务内存延迟监控
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
#是否激活重置哈希
activerehashing yes
#客户端输出缓冲区限制
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 512mb 128mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
#复制积压缓存区大小
repl-backlog-size 256mb
#redis server执行后台任务的频率,默认为10
hz 10
#最大客户端连接数
maxclients 15000
也可以直接pull我打好的镜像:
docker pull registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
由于是容器方式运行,所以redis.conf配置可以完全一样:
#设置为守护进程
daemonize no
#Redis运行的进程pid文件
pidfile /log/redis-8000.pid
#Redis服务端口号
port 8000
#Redis服务绑定ip
#bind 192.168.100.144
bind 0.0.0.0
#最大内存
maxmemory 8gb
#开启集群模式
cluster-enabled yes
#节点配置文件
cluster-config-file nodes-8000.conf
#集群节点超时时间(单位:毫秒)
cluster-node-timeout 15000
#集群是否需要所有的slot都分配给在线节点,才能正常访问
cluster-require-full-coverage no
2.2 创建Redis容器
创建Redis容器只需要使用第一步下载好的Redis镜像,调用Docker的run命令即可:
docker run -it -d --name redis-master1 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
docker run -it -d --name redis-master2 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
docker run -it -d --name redis-master3 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
docker run -it -d --name redis-slave1 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
docker run -it -d --name redis-slave2 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
docker run -it -d --name redis-slave3 registry.cn-hangzhou.aliyuncs.com/smallsoup/node-redis:0.4
容器启动完毕后,可以运行docker ps -a查看容器启动状态:
image.png
可以查看日志文件:
image.png有日志文件可得,节点已经启动成功。这个日志文件是Redis服务器普通的日志文件,在集群模式下,第一次也会自动创建一个日志文件,由配置文件cluster-config-file指定文件。
集群配置文件的作用:当集群内节点发生信息变化时,如添加节点、节点下线、故障转移等。节点会自动保存集群的状态到配置文件中。该配置文件由Redis自行维护,不要手动修改,防止节点重启时产生集群信息错乱。
我们来查看一下,集群模式的日志文件:(以下截图是3主3从搭建好之后的截图。容器刚创建后进入容器看到的会有不同)
image.png也可以通过客户端连接该节点,通过命令CLUSTER NODES来查看:
image.png
2.3 节点握手
节点握手是指一批运行在集群模式的节点通过Gossip协议彼此通信,达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet <ip> <port>,可以使用docker inpect <容器id>查看所有容器的IP地址:
image.png在master1(容器IP为 172.17.0.2)的容器内执行以下命令,使得master1和master2、master3、salve1、salve2、salve3通信:
root@c121ba5c04c7:/data# redis-cli -h 127.0.0.1 -p 8000
127.0.0.1:8000> CLUSTER MEET 172.17.0.4 8000
OK
127.0.0.1:8000> CLUSTER MEET 172.17.0.3 8000
OK
127.0.0.1:8000> CLUSTER MEET 172.17.0.5 8000
OK
127.0.0.1:8000> CLUSTER MEET 172.17.0.6 8000
OK
127.0.0.1:8000> CLUSTER MEET 172.17.0.7 8000
OK
这样可以让所有的节点都互相感知。可以通过cluster nodes命令查看。
2.4 分配槽
可以看一下8000端口的槽个数
127.0.0.1:8000> CLUSTER INFO
cluster_state:fail
cluster_slots_assigned:0 // 被分配槽的个数为0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:479
cluster_stats_messages_received:479
接下来为节点分配槽空间。通过cluster addslots命令,可在master1容器内执行:
root@c121ba5c04c7:/data# redis-cli -h 127.0.0.1 -p 8000 cluster addslots {0..5461}
OK
root@c121ba5c04c7:/data# redis-cli -h 172.17.0.3 -p 8000 cluster addslots {5462..10922}
OK
root@c121ba5c04c7:/data# redis-cli -h 172.17.0.4 -p 8000 cluster addslots {10923..16383}
我们将16383个槽平均分配给master1、master2、master3的节点(容器)。再次执行CLUSTER INFO查看一下集群的状态:
127.0.0.1:6379> CLUSTER INFO
cluster_state:ok // 集群状态OK
cluster_slots_assigned:16384 // 已经分配了所有的槽
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:1212
cluster_stats_messages_received:1212
可以通过CLUSTER NODES来查看分配情况。
127.0.0.1:8000> cluster nodes
31a8fc4031f74ea131632369406d257a60eff668 172.17.0.4:8000 master - 0 1552396086679 2 connected 10923-16383
c0404f497843cfd886f1c56f44759d74dac4fe36 172.17.0.5:8000 master - 0 1552396089697 0 connected
bd1086a2b9812492bd10031981df27f792d59073 172.17.0.2:8000 myself,master - 0 0 4 connected 0-5461
bd894d71f720098abc8c2c63675daa45a879f09e 172.17.0.6:8000 master - 0 1552396090704 5 connected
55f3c9e85ae393efd83551f69db2f05f1f8c7060 172.17.0.7:8000 master - 0 1552396087685 3 connected
4b61235b5ad5139e9a39f2b3608c64951b200c6f 172.17.0.3:8000 master - 0 1552396088689 1 connected 5462-10922
目前还有三个节点(容器)没有使用,作为一个完整的集群,每个负责处理槽的节点应该具有从节点,保证当主节点出现故障时,可以自动进行故障转移。集群模式下,首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽的信息和相关数据。
使用cluster replicate <nodeid>在从节点(容器)上执行,在slave1、salve2、savle3上分别执行:
#salve1容器内执行
redis-cli -h 127.0.0.1 -p 8000 cluster replicate bd1086a2b9812492bd10031981df27f792d59073
#salve2容器内执行
redis-cli -h 127.0.0.1 -p 8000 cluster replicate 31a8fc4031f74ea131632369406d257a60eff668
#salve3容器内执行
redis-cli -h 127.0.0.1 -p 8000 cluster replicate 4b61235b5ad5139e9a39f2b3608c64951b200c6f
这样就完成了一个3主3从的Redis集群搭建。如下图所示:
image.png遇到的问题:
搭建完成后,验证主从复制,在容器内执行get、set操作有问题,报错:
(error)MOVED 15495 172.17.0.4::8000
image.png
原因
这种情况一般是因为启动redis-cli时没有设置集群模式所导致。
解决方案
启动时使用-c参数来启动集群模式,命令如下:
redis-cli -c -h 172.17.0.2 -p 8000
测试OK
image.png可以看到,测试主从复制成功。在master容器中输入set a b命令,在其他容器内可以get到。
参考:
Redis 学习笔记(十四)Redis Cluster介绍与搭建
(error) MOVED 原因和解决方案
Docker:创建Redis集群
网友评论