06-Redis系列之-哨兵(Redis-Sentinel)和集群详解和搭建
主从架构高可用
主从架构存在的问题
- 主从复制,主节点发生故障,需要做故障转移。(可以手动转移:让其中一个slave变成master)
- 主从复制,只有主写数据,所以写能力和存储能力有限
总结:redis
的主从架构模式只能够帮redis
分担读的压力,但是这个架构有一个非常致命的缺陷,一旦master节点挂掉了,整个集群将无法写入数据,这并不符合redis
高可用架构的特点。
哨兵
哨兵架构
主从架构是高可用架构的基石,哨兵架构是主从架构的升级版,主从架构master节点和slave节点是一开始就定好了,而在哨兵模式中,master节点是可以转移的,一旦发现当前master节点宕机了,哨兵底层会通过选举算法,指定一个slave节点晋升为master,保证在任何情况下,都有master节点可以支持写操作,也间接性支持写和读的高可用了。
哨兵的主要作用之一:自动故障转移。
做故障判断,故障转移,通知客户端(其实是一个进程),客户端直接连接sentinel的地址
哨兵的几种作用
- 监控:不断检查master和slave节点是否正常运行,检查主节点是否存活,主节点和从节点运行情况
- 通知:检测到节点出现问题时,向其他的哨兵节点和客户端发送通知
- 自动故障转移:当检测到master节点宕机了,将会断开与master节点连接的所有slave节点,然后从slave节点中选举一个节点作为master节点,然后自动将其他的slave连接到最新的master节点,并告知客户端最新的服务器地址
流程详解
-
多个sentinel发现并确认master有问题
-
选举出一个sentinel作为领导
-
选取一个slave作为新的master
-
通知其余slave成为新的master的slave
-
通知客户端主从变化
-
等待老的master复活成为新master的slave
操作和配置
参数详解
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
# monitor(sentinel monitor <master-name> <ip> <redis-port> <quorum>)
mymaster名字可以自定义,ip地址为master节点的地址 后边的 2 代表的是,如果有俩个哨兵判断这个主节点挂了那这个主节点就挂了,通常设置为哨兵个数一半加一。只有当半数以上的slave节点认为master节点宕机了,才会进行选举master节点,否则不会进行选举,
# down-after-milliseconds(sentinel down-after-milliseconds <master-name> <milliseconds> )
哨兵连接主节点多长时间没有响应就代表挂了。后边 30000 是毫秒,也就是 30 秒(默认配置)。
# parallel-syncs(sentinel parallel-syncs <master-name> <numslaves> )
这个配置项是指在故障转移时,最多有多少个从节点对新的主节点进行同步,这个值越小完成故障转移的时间就越长,这个值越大就意味着越多的从节点因为同步数据而不可用,可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# failover-timeout(sentinel failover-timeout <master-name> <milliseconds>)
1.同一个sentinel对同一个master两次failover之间的间隔时间。
2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
# sentinel auth-pass <master-name> <password>
设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
操作过程
正常操作
# 让redis的主从复制高可用,搭一个一主两从
# 过滤无用信息(查看sentinel配置文件):cat sentinel.conf | grep -v '#' | grep -v '^$'
1.创建目录
mkdir -p /home/redis/data1/ /home/redis/data2/ /home/redis/data3/
2.创建三个配置文件
# 第一个是主配置文件
vim /home/conf/6379.conf
daemonize yes
port 6379
dir "/home/redis/data1/"
logfile "6379.log"
# 第二个是从配置文件
vim /home/conf/6378.conf
daemonize yes
port 6378
dir "/home/redis/data2/"
logfile "6378.log"
slaveof 127.0.0.1 6379
slave-read-only yes
# 第三个是从配置文件
vim /home/conf/6377.conf
daemonize yes
port 6377
dir "/home/redis/data3/"
logfile "6377.log"
slaveof 127.0.0.1 6379
slave-read-only yes
3.把三个redis服务都启动起来
redis-server 6379.conf
redis-server 6378.conf
redis-server 6377.conf
4.搭建哨兵
# sentinel.conf这个文件
# 把哨兵也当成一个redis服务器
# 创建三个配置文件
vim sentinel_26379.conf
vim sentinel_26378.conf
vim sentinel_26377.conf
# 内容如下(需要修改端口,文件地址日志文件名字)
port 26379
daemonize yes
dir "/home/redis/data1/"
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
# monitor:mymaster名字可以自定义,ip地址为master节点的地址 后边的 2 代表的是,如果有俩个哨兵判断这个主节点挂了那这个主节点就挂了,通常设置为哨兵个数一半加一。只有当半数以上的slave节点认为master节点宕机了,才会进行选举master节点,否则不会进行选举,
# down-after-milliseconds:哨兵连接主节点多长时间没有响应就代表挂了。后边 30000 是毫秒,也就是 30 秒(默认配置)。
# parallel-syncs:这个配置项是指在故障转移时,最多有多少个从节点对新的主节点进行同步,这个值越小完成故障转移的时间就越长,这个值越大就意味着越多的从节点因为同步数据而不可用,可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# failover-timeout
1.同一个sentinel对同一个master两次failover之间的间隔时间。
2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
port 26378
daemonize yes
dir "/home/redis/data2/"
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel26378.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
port 26377
daemonize yes
dir "/home/redis/data3/"
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel26377.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
5.启动三个哨兵
redis-sentinel sentinel_26379.conf
redis-sentinel sentinel_26378.conf
redis-sentinel sentinel_26377.conf
# 查看启动的服务
ps aux | grep redis
6.登陆哨兵
redis-cli -p 26379
# 输入 info 查看信息
# 查看哨兵的配置文件被修改了(自动生成的)
cat entinel_26379.conf
7.测试(开三个窗口,进入到三个redis中)
redis-cli -p 6379
redis-cli -p 6378
redis-cli -p 6377
测试结果:6378和6377中不能写数据,只有6379(主)能写入数据
# 主动停掉主6379,哨兵会自动选择一个从库作为主库
redis-cli -p 6379
shutdown
在选出的主库中增加点数据...
8.重新启动6379(启动后等待一会,会同步没有启动时其他库中操作的数据)
# 原来的主库重新启动后会变成从库
redis-server 6379.conf
redis-cli -p 6379.conf
docker操作
cd /home/
mkdir -p redis4/conf redis4/data redis5/conf redis5/data redis6/data redis6/conf
vim /home/redis4/conf/sentinel.conf
port 26379
daemonize yes
dir data
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
cp vim /home/redis4/conf/sentinel.conf vim /home/redis5/conf/sentinel.conf
cp vim /home/redis4/conf/sentinel.conf vim /home/redis6/conf/sentinel.conf
docker run -p 26379:26379 --name redis_26379 -v /home/redis4/conf/:/etc/redis/ -v /home/redis4/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf
docker run -p 26378:26379 --name redis_26378 -v /home/redis5/conf/:/etc/redis/ -v /home/redis5/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf
docker run -p 26377:26379 --name redis_26377 -v /home/redis6/conf/:/etc/redis/ -v /home/redis6/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf
redis-sentinel sentinel.conf
info
哨兵订阅(了解一下)
1.开启本地 Redis 服务,开启两个 redis-cli 客户端。
2.在第一个 redis-cli 客户端输入 SUBSCRIBE runoobChat,意思是订阅 runoobChat 频道。
3.在第二个 redis-cli 客户端输入 PUBLISH runoobChat "Redis PUBLISH test" 往 runoobChat 频道发送消息,这个时候在第一个 redis-cli 客户端就会看到由第二个 redis-cli 客户端发送的测试消息。
客户端连接
import redis
from redis.sentinel import Sentinel
# 连接哨兵服务器
# 10.0.0.10:26379
sentinel = Sentinel(
[
('10.0.0.10', 26379),
('10.0.0.10', 26378),
('10.0.0.10', 26377)
],
socket_timeout=5
)
print(sentinel)
# 获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)
# 获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)
# 读写分离
# 获取主服务器进行写入
master = sentinel.master_for('mymaster', password='root123456', db=0, socket_timeout=0.5)
w_ret = master.set('like', 'girl')
# 获取从服务器进行读操作
slave = sentinel.slave_for('mymaster', password='root123456', db=0, socket_timeout=0.5)
r_ret = slave.get('like')
print(r_ret)
封装一下
from redis.sentinel import Sentinel
SOCKET_TIMEOUT = 10
class RedisSentinel:
def __init__(self, sentinel_list, name="mymaster", password="root123456", db=0):
self.sentinel = Sentinel(sentinel_list, socket_timeout=SOCKET_TIMEOUT)
self.name = name
self.password = password
self.db = db
def get_master_and_slave_conn(self):
master = self.sentinel.master_for(
service_name=self.name,
socket_timeout=SOCKET_TIMEOUT,
password=self.password,
db=self.db)
slave = self.sentinel.slave_for(
service_name=self.name,
socket_timeout=SOCKET_TIMEOUT,
password=self.password,
db=self.db
)
return master, slave
redis_sentinel = RedisSentinel(
[
('10.0.0.10', 26379),
('10.0.0.10', 26378),
('10.0.0.10', 26377)
]
)
master, slave = redis_sentinel.get_master_and_slave_conn()
w_ret = master.set('program', 'python')
r_ret = slave.get('program')
print(r_ret)
哨兵模式工作原理
哨兵是一个分布式系统,可以在一个架构中运行多个哨兵进程,这些进程使用流言协议(gossip protocols)来传播Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。哨兵模式的具体工作原理如下:
-
心跳机制
- Sentinel 与 Redis Node:Redis Sentinel 是一个特殊的 Redis 节点。在哨兵模式创建时,需要通过配置指定 Sentinel 与 Redis Master Node 之间的关系,然后 Sentinel 会从主节点上获取所有从节点的信息,之后 Sentinel 会定时向主节点和从节点发送 info 命令获取其拓扑结构和状态信息。
- Sentinel与Sentinel:基于 Redis 的订阅发布功能, 每个 Sentinel 节点会向主节点的 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息 ,同时每个 Sentinel 节点也会订阅该频道, 来获取其他 Sentinel 节点的信息以及它们对主节点的判断
- 通过以上两步所有的 Sentinel 节点以及它们与所有的 Redis 节点之间都已经彼此感知到,之后每个 Sentinel 节点会向主节点、从节点、以及其余 Sentinel 节点定时发送 ping 命令作为心跳检测, 来确认这些节点是否可达。
-
判断master节点是否下线
-
每个 sentinel 哨兵节点每隔1s 向所有的master、slave以及其他 sentinel 节点发送一个PING命令,作用是通过心跳检测,检测主从服务器的网络连接状态
-
如果 master 节点回复 PING 命令的时间超过 down-after-milliseconds 设定的阈值(默认30s),则这个 master 会被 sentinel 标记为主观下线,修改其 flags 状态为SRI_S_DOWN
-
当sentinel 哨兵节点将 master 标记为主观下线后,会向其余所有的 sentinel 发送sentinel is-master-down-by-addr消息,询问其他sentinel是否同意该master下线
发送命令:sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid> ip:主观下线的服务ip port:主观下线的服务端口 current_epoch:sentinel的纪元 runid:*表示检测服务下线状态,如果是sentinel的运行id,表示用来选举领头sentinel
-
每个sentinel收到命令之后,会根据发送过来的 ip和port 检查自己判断的结果,回复自己是否认为该master节点已经下线了
回复内容主要包含三个参数(由于上面发送的runid参数是*,这里先忽略后两个参数) down_state(1表示已下线,0表示未下线) leader_runid(领头sentinal id) leader_epoch(领头sentinel纪元)。
-
sentinel收到回复之后,如果同意master节点进入主观下线的sentinel数量大于等于quorum,则master会被标记为客观下线,即认为该节点已经不可用。
-
在一般情况下,每个 Sentinel 每隔 10s 向所有的Master,Slave发送 INFO 命令。当Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次。作用:发现最新的集群拓扑结构
-
-
基于Raft算法选举领头sentinel
- 判断客观下线的sentinel节点向其他 sentinel 节点发送 SENTINEL is-master-down-by-addr ip port current_epoch runid
- 目标sentinel回复是否同意master下线并选举领头sentinel,选择领头sentinel的过程符合先到先得的原则。举例:sentinel1判断了客观下线,向sentinel2发送了第一步中的命令,sentinel2回复了sentinel1,说选你为领头,这时候sentinel3也向sentinel2发送第一步的命令,sentinel2会直接拒绝回复
- 当sentinel发现选自己的节点个数超过 majority 的个数的时候,自己就是领头节点
- 如果没有一个sentinel达到了majority的数量,等一段时间,重新选举
-
故障转移
-
在进行选择之前需要先剔除掉一些不满足条件的slaver,这些slaver不会作为变成master的备选
- 剔除列表中已经下线的从服务
- 剔除有5s没有回复sentinel的info命令的slave
- 剔除与已经下线的主服务连接断开时间超过 down-after-milliseconds * 10 + master宕机时长 的slaver
-
选主过程:
-
选择优先级最高的节点,通过sentinel配置文件中的replica-priority配置项,这个参数越小,表示优先级越高
-
如果第一步中的优先级相同,选择offset最大的,offset表示主节点向从节点同步数据的偏移量,越大表示同步的数据越多
-
如果第二步offset也相同,选择run id较小的
-
-
-
修改配置
-
领头sentinel会对选出来的从节点执行slaveof no one 命令让其成为主节点
-
领头sentinel 向别的slave发送slaveof命令,告诉他们新的master是谁谁谁,你们向这个master复制数据
-
如果之前的master重新上线时,领头sentinel同样会给起发送slaveof命令,将其变成从节点
-
哨兵总结
- 每个哨兵节点,只需要配置监控主节点,便可以自动发现其他的哨兵节点和从节点。
- 在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写
- 哨兵节点本质上是
redis
节点
集群
集群是3.0以后加的,3.0----5.0之间,ruby脚本,5.0以后,内置了ruby脚本
通过分片集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,而且也具有故障迁移(主从切换)的功能,实现了较为完善的高可用方案。
架构图
集群搭建
停止服务
0.停掉上面哨兵步骤中开启的redis-server和redis-sentinel
pkill -9 redis-server
pkill -9 redis-sentinel
查看一下服务是否停止:ps aux | grep redis
配置
1.配置文件
cd /home/conf
vim redis_7000.conf
daemonize yes
port 7000
dir "/hkw/redis/data"
logfile "redis_7000.log"
dbfilename "dump_7000.rbd"
cluster-enabled yes
cluster-node-timeout 15000
cluster-config-file nodes_7000.conf
cluster-require-full-coverage yes
# 配置文件详解
bind 192.168.119.128(绑定当前电脑的 IP,这是我虚拟机的,你绑定成你虚拟机的ip)
port 7000(因为我这是一台机器运行6个redis实例,所以要启动6个实例,得为它配置6个不同的端口,若你是6台机器,默认的端口就行,无需更改)
daemonize yes(这是设置是否后台启动 Redis,默认 no ,但是生产环境肯定要默认就开启 Redis,所以这里设置为 yes 。)
pidfile /var/run/redis_7000.pid(_9001这个一定要和第一个配置的端口一样)
dir ./(数据文件存放位置,我换成指定目录下存放)
cluster-enabled yes(启动集群模式)
cluster-config-file nodes_7000.conf(节点的配置文件,7000这个也要和之前的端口对应)
cluster-node-timeout 15000(超时时间,自动带了哨兵)
sed同步多个配置文件
2.快速同步多个配置文件
sed 's/7000/7001/g' redis_7000.conf > redis_7001.conf
sed 's/7000/7002/g' redis_7000.conf > redis_7002.conf
sed 's/7000/7003/g' redis_7000.conf > redis_7003.conf
sed 's/7000/7004/g' redis_7000.conf > redis_7004.conf
sed 's/7000/7005/g' redis_7000.conf > redis_7005.conf
开启redis服务
3.开启redis-server
redis-server redis_7000.conf
redis-server redis_7001.conf
redis-server redis_7002.conf
redis-server redis_7003.conf
redis-server redis_7004.conf
redis-server redis_7005.conf
启动集群
4.启动集群(这里的1表示三主三从,配置成2表示要有9太机器-三主六从)
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
查看信息
5.连接进去看看信息
redis-cli -p 7000
CLUSTER info
CLUSTER nodes # 集群节点信息
cluster slots # 查看槽的信息
自动切换
6.自动切换槽(读写自动切)
redis-cli -c -p 7000
"""
停掉一台主,看看会有什么效果?
关掉其中一个主,另一个从立马变成主顶上去,重起停止的主,会自动变成从
"""
集群扩容和缩容
集群扩容
7.扩展一台机器(自动分配槽)
7.1快速分配配置文件
sed 's/7000/7006/g' redis_7000.conf > redis_7006.conf
sed 's/7000/7007/g' redis_7000.conf > redis_7007.conf
7.2启动(启动后的这两台redis是被孤立的,跟集群没有关系)
redis-server redis_7006.conf
redis-server redis_7007.conf
查看一下:redis-cli -p 7006 cluster nodes
7.3加入集群当中(前面的7000~7005任何一台都可以,因为集群之间是互通的)
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7000 cluster meet 127.0.0.1 7007
查看nodes(已加入集群):redis-cli -p 7006 cluster nodes
7.4分配主从(7006作为7007的主)
redis-cli -p 7007 cluster replicate 7006的id
7.4分配槽
redis-cli --cluster reshard 127.0.0.1:7000
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 7006的id
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: all
Do you want to proceed with the proposed reshard plan (yes/no)? yes
7.5查看槽信息
redis-cli -p 7000 cluster nodes
集群缩容
8.减少一台机器(收缩槽)
8.1下线迁槽
redis-cli --cluster reshard --cluster-from 7006的id(主) --cluster-to 7000的id(主) --cluster-slots 1366 127.0.0.1:7000
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7001的id(主) --cluster-slots 1366 127.0.0.1:7001
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7002的id(主) --cluster-slots 1366 127.0.0.1:7002
8.2下线节点,关闭节点(先下线从,再下线主,因为主先下的话会触发故障转移)
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7007id
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7006id
8.3查看槽信息(此时7006和7007已经脱离集群)
redis-cli -p 7000 cluster nodes
8.4关闭06,07的redis服务
redis-cli -p 7006 shutdown
redis-cli -p 7007 shutdown