Redis 集群是一个提供在多个Redis节点间共享数据的程序集
通过集群的配置可以解决:机器故障、容量瓶颈、QPS瓶颈等
主从同步
redis主从结构有一主多从和级联结构
一主多从:master将数据同步给多个slave节点
级联结构:主从结构下支持从从同步,可以减轻主节点的同步负担
运用场景:
- 数据备份:主节点将数据同步给从节点,当主节点服务发生异常,可以切换到从节点继续提供服务
- 读写分离:写请求只发给主节点,读请求可以发给任何一个节点,在高并发的场景下,由于从服务分担了一部分读请求,可以使系统整体的吞吐量提升
主从同步机制
redis主从同步分为两个过程:全量同步和增量同步
从节点刚加入集群时会进行一次全量同步,然后通过增量同步将主节点的数据同步到从节点
首先了解三个概念:
- runid:服务器运行id,可以唯一标识redis节点
- offset:复制偏移量,从节点用来记录数据同步位置
- buffer:主节点会将客户端请求的写命令缓存到buffer中
全量同步
- slave连接master并发送同步命令 psync ? -1(?为 master runid,-1为slave offset,第一次建立连接slave不知道master的runid,也没有偏移量)
- master接收命令判断出当前为全量复制,将runid和offect发送给slave
- master fork子进程执行bgsave,此时master正常接收外部请求并缓存客户端的写命令
- slave接收rdb文件,清空旧数据后将rdb文件加载到内存
- master收到从节点rdb加载完成的信息后,将缓存区内的数据发送给slave
- slave接收并更新数据
注:master在执行bgsave的同时接收客户端的请求,原因是fork子进程的时候操作系统采用写时复制(copy-on-write)策略,当父进程执行写操作时,会将数据页面复制一份出来进行修改
增量同步
redis同步的是指令流
- 主节点将写操作的命令记录buffer,异步写入到从节点
- 从节点接收并更新数据,每隔1s向master发送REPLCONF ACK命令反馈自己的偏移量
注:buffer是一个定长的环形数组,当空间被写满时,会从头开始覆盖前面的内容
因此如果任意一个从节点掉线时间过长,没有同步的指令有可能已经被后续的指令流覆盖掉,这时候需要重新做全量复制
sentinel(哨兵)
主从备份的情况下,当主节点挂掉时需要将其中一台从节点升为主节点,继续提供服务,这时需要人工也不高效,sentinel就是为此设计的,当发生故障时可以自动进行主从切换
sentinel通过ping以及info来持续监控和发现节点变化
客户端如何向集群读写:
client端首先会连接sentinel,向sentinel询问master或slave的地址,根据返回IP地址连接访问
# 通过discover_master、discover_slave方法获取所有master IP、slave IP
sentinel.discover_master('redis-cluster')
sentinel.discover_slave('redis-cluster')
# 通过master_for、slave_for方法可以从连接池拿出一个连接使用
sentinel.master_for('redis-cluster')
sentinel.slave_for('redis-cluster')
分布式集群
主从同步可以解决机器故障,也可以提升系统整体的吞吐量,但当数据量越来越大的时候主从也无能为力,分布式集群可以很好的解决系统容量瓶颈
分布式的情况下:
- 节点之间是相互通信
- 每个节点都会负责读写
槽位分片原理
redis 采用的是哈希槽的概念进行数据分片,内置16384个槽位
- redis key 通过crc16算法得到一个哈希值
- 哈希值对16384取模来确定槽位
计算公式: crc16(key)% 16384
集群搭建
接下来将搭建一个简单的redis集群,了解它组成的方式以及内部通信的基本模型,集群结构如下:
node1(6380),slave1(6383)
node2(6381),slave2(6384)
node3(6382),slave3(6385)
搭建流程
1、启动所有redis节点
创建node1配置文件node1.conf
port 6380
daemonize yes
logfile "6380.log"
dir "/opt/redis/data/"
dbfilename "dump-6380.rdb"
cluster-enabled yes
cluster-config-file nodes-6380.conf
cluster-require-full-coverage no
bind 0.0.0.0
启动node1服务执行: redis-server node1-conf
查看服务是否启动成功:redis-servps -ef | grep redis
( 以此类推修改配置文件端口号启动剩余5个节点 )
2、meet操作
集群中节点之间是相互通信的,所有节点共享信息,但在这之前需要先认识对方,也就是需要执行meet操作,假设A B之间相互认识,A C之间相互认识,那么通过 A,B和C也就自然认识了
meet操作执行:
redis-cli -p 6380 cluster meet 127.0.0.1 6381
redis-cli -p 6380 cluster meet 127.0.0.1 6382
redis-cli -p 6380 cluster meet 127.0.0.1 6383
redis-cli -p 6380 cluster meet 127.0.0.1 6384
redis-cli -p 6380 cluster meet 127.0.0.1 6385
执行:redis-cli -p 6380 cluster nodes
3、指派槽
添加槽脚本:script add_slot.sh
for slot in $(seq $1 $2)
do
redis-cli -p $3 cluster addslots ${slot}
done
集群内置16384个槽位,为每个节点分配槽执行:
sh add_slot.sh 0 5461 6380
sh add_slot.sh 5462 10922 6381
sh add_slot.sh 10923 16383 6382
执行:redis-cli -p 6380 cluster nodes
4、主从复制
slave节点复制对应的master节点,执行:
redis-cli -p 6383 cluster replicate 35674ee715d50b485a4c8d8ffb9d3a8484516ab3
redis-cli -p 6384 cluster replicate b7b0fe7fcf686157d344b156324fda6e9d457ece
redis-cli -p 6385 cluster replicate 894f41ea105c774bdd54d90869e409991d88158c
执行:redis-cli -p 6380 cluster nodes
执行:redis-cli -p 6380 cluster info
可以看到集群搭建成功!!!
注:以上是redis集群原生搭建的过程,也可以使用第三方工具redis-trib快速搭建集群
apt install ruby-full
gem install redis
find / -name redis-trib.rb
cp /usr/share/doc/redis-tools/examples/redis-trib.rb /usr/local/bin/redis-trib.rb
redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.
集群扩容
在集群中添加一个新的节点node4,以及从节点slave4
node4(6386),slave4(6387)
前3个步骤之前一样
# 创建配置文件
sed 's/6380/6386/g' node1.conf > node4.conf
sed 's/6380/6387/g' node1.conf > slave4.conf
# 启动 node4、slave4 redis server
redis-server node4-conf
redis-server slave-conf
# meet 加入集群
redis-cli -p 6380 cluster meet 127.0.0.1 6386
redis-cli -p 6380 cluster meet 127.0.0.1 6387
# 主从复制,将slave4配置成node4的从节点
redis-cli -p 6380 cluster nodes
redis-cli -p 6387 cluster replicate 6b314fc95063e73b4b1daa947674fdc5cb64fb21
执行:redis-cli -p 6380 cluster nodes
槽迁移
集群内置的16384个槽位,已经全部分给了node1、noed2、node3,新节点node4 加入集群需要将前3个节点的部分槽迁移进去
我们借助redis-trib进行槽迁移执行:redis-trib.rb reshard 127.0.0.1:6380
执行:redis-cli -p 6380 cluster nodes | grep master
可以看到0-1365、5462-628、10923-12287的槽位都迁移到新节点node4中了,集群扩容完成
集群缩容
集群缩容分两步下线槽迁移和忘记节点
# 下线槽迁移,从node4节点分别迁移到其它3个节点,并指定迁移槽数量
redis-trib.rb reshard --from 6b314fc95063e73b4b1daa947674fdc5cb64fb21 --to 35674ee715d50b485a4c8d8ffb9d3a8484516ab3 --slots 1366 127.0.0.1:6380
redis-trib.rb reshard --from 6b314fc95063e73b4b1daa947674fdc5cb64fb21 --to b7b0fe7fcf686157d344b156324fda6e9d457ece --slots 1365 127.0.0.1:6381
redis-trib.rb reshard --from 6b314fc95063e73b4b1daa947674fdc5cb64fb21 --to 894f41ea105c774bdd54d90869e409991d88158c --slots 1365 127.0.0.1:6382
# 忘记节点(先 slave 后 master)
redis-trib.rb del-node 127.0.0.1:6380 8db6504e8829e0595d046a08f6d8596c5289c352
redis-trib.rb del-node 127.0.0.1:6380 6b314fc95063e73b4b1daa947674fdc5cb64fb21
执行:redis-cli -p 6380 cluster nodes
可以看到node4、slave4都不在集群中了,slot也分别迁移回了其它3个节点,集群缩容完成
客户端路由
redis集群中节点之间是相互通信,每个节点都会负责读写,那么如果查询的key不在请求的节点上的,是如何返回查询结果呢?
moved重定向
- 客户端请求任意节点,查询key1
- 节点收到请求计算slot值
如果slot刚好在node1上,则直接返回value
如果slot不在则返回solt所在的节点,客户端重新发送请求
进行集群的扩容和缩容都会有slot迁移的过程,如果slot正在迁移过程中,客户端发送请求,这时redis服务如何返回查询结果?
ask 重定向
- 客户端请求一个正在进行槽迁移的源节点
- 服务端发现数据不在源节点,返回 -ASK targetNodeAddr
-客户端发送ASKING到迁移节点
-迁移节点响应后,客户端再次向目标节点发送查询请求
OK,到这里redis主从以及分布式集群你应该有一个大致的了解