241_Redis_集群_主从架构
一 分布式理论基石CAP
- C consistent 一致性
- A availability 可用性
- P Partition tolerance 分区容忍性
分布式的前提:分布式节点在不同机器上做网络隔离,意味着必然有网络断开的风险, 这种场景叫 网络分区
当网络分区时 发生网络故障时, 一致性和可用性二选一
- 1 分布式节点之间无法进行通信,对A 节点操作无法同步到另一个节点B,一致性无法进行满足
- 2 暂停服务修复 A –B 之间的通信, 这就是牺牲了可用性
最终一致性
Redis 主从数据时异步同步的,Redis在定义上不满足一致性的要求,客户端在Redis上修改数据后, 立即返回,不考虑slave是否接收到, 即使主从断开,主依旧对外提供服务。但是Redis保证最终一致性, slave在网络恢复后 采用多种策略追赶, 保持与主节点一致
二 BASE理论
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结,是基于CAP定律逐步演化而来。
其核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案
BASE其实是下面三个术语的缩写:
基本可用(Basically Available):
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态(Soft State): 软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。MySQL Replication 的异步复制也是一种体现。
最终一致性(Eventual Consistency): 最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成
Redis主从命令
配置slave
redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380
redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380
查看master slave
redis-cli -p 6382 -a 123 info replication
停止主从
SLAVEOF NO ONE
redis.conf
replica-priority 100 # 数字越小级别越高,越优先升级为主 旧版本slave-priority
Redis 主从原理
1 主从第一阶段 同步(psync rdb snapshot)
1.1 slave 库通过slaveof ip 6379命令,连接主库,并发送PSYNC给主库 (slave 执行psync $master_runid $slave_offset)
#master_runid是主库redis实例的一个唯一标识id,是redis实例创建的时候自动生成的。offset是指从节点的复制偏移量
1.2 master收到PSYNC,会立即触发BGSAVE,后台保存RDB,发送给slave
Master fork一个bgsave子进程生成一个RDB快照文件发送给从库,由slave完成加载
#主库收到这个请求后,把自己的runid和offset发送给从库 (master执行 FULLRESYNC $master_runid $master_offset)
1.3 副本库接收后会应用RDB快照
2 主从第二阶段 命令传播(commands propagation)
2.1主库只要发生新的操作,保存并发送给slave都会以命令传播的形式自动发送给副本库
2.2 所有复制相关信息,从info replication信息中都可以查到.即使重启任何节点,他的主从关系依然都在
2.3 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
2.4 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的
Redis 核心复制同步 快照同步&增量同步
1 快照同步
Master :主节点进行bgsave,将内存中数据落盘后 发送给 slave
Slave: 收到快照后进行全量加载,加载前要将自己内存数据清空, 加载完毕后通知master 进行增量同步
注意:
快照同步时,master的复制buffer不停前移,如果快照时间过长或者复制buffer过小, 会导致同步期间 复制buffer被覆盖,
导致无法进行增量复制,会再次发起快照同步,陷入死循环. 合理配置buffer参数大小 (replication buffe)
replication buffer的作用
replication buffer里面存放的数据是下面三个时间内master数据更新操作:
master执行rdb bgsave产生snapshot的时间内master变更的数据
master发送rdb到slave网络传输时间内master变更的数据
slave load rdb文件把数据恢复到内存的时间内 master变更的数据
主从全量同步过程中,主库是不会阻塞的,这个时候还会继续接收新的写请求, 并写入replication buffer
redis server会为每一个连接到自己的客户端创建一个replication buffer,用来缓存主库执行的命令。
等从库加载完成RDB文件后,主库就会把缓存的命令发送给从库,完成全量最终的全量同步
如果replication buffer写满了,无论客户端是普通客户端还是从库,只能断开跟这个客户端的连接了。这样从库全量同步失败,只能再次尝试全量同步
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
class: 客户端种类,包括Normal,Slaves和Pub/Sub
Normal: 普通的客户端。默认limit 是0,也就是不限制。
Pub/Sub: 发布与订阅的客户端的。默认hard limit 32M,soft limit 8M/60s。
Slaves: 从库的复制客户端。默认hard limit 256M,soft limit 64M/60s。
hard limit: 缓冲区大小的硬性限制。
soft limit: 缓冲去大小的软性限制。
soft seconds: 缓冲区大小达到了(超过)soft limit值的持续时间
slave表示这个缓冲区是主从复制缓存区,256mb表示缓冲区最大是 256mb 64mb 60表示如果60s内写入量超过64mb,缓冲就会溢出。
把缓冲区最大值调大,或者把每秒允许的写入量增加,可以减小replication buffer溢出的概率
redis server会为每一个客户端分配一个replication buffer,这样redis server连接的客户端越多,对内存的损耗也越大,
所以控制从节点的数量,也可以控制缓存区对内存的开销。也可以控制主节点的写命令接收速率
2 增量同步
Redis 同步的是指令流,
master: 将数据修改指令落到本地内存的buffer中, 异步将buffer同步到slave节点
slave: 执行同步过来的指令流 同时向master反馈同步的偏移量
redis从库加载完成RDB文件后,就会继续同步从库的增量写命令。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致
解决方案 repl_backlog_buffer环形缓冲区 (参数 repl-backlog-size)
repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己 已经读到的位置
1 主库的写命令,除了传给从库后,还会写入repl_backlog_buffer
2 当主从断开后,重新建立连接,从库会发送之前的那个命令:psync $master_runid $offset (slave 发送slave_repl_offset 发给主库)
3 主库就会在repl_backlog_buffer中找到offset的位置,把之后的写命令写入replication buffer同步给从库 (master 判断master_repl_offset 和 slave_repl_offset 之间的差距)
4 如果repl_backlog_buffer已经能被覆盖, 则会触发快照同步
备注 所有slave从库共享一个 repl_backlog_buffer (环形结构)
其大小可以根据 估算积压缓冲区的大小repl-backlog-size值不小于这两者的乘积。
缓冲空间大小 =主库更新 命令 *((master执行rdb bgsave的时间)+ (master发送rdb到slave的时间) + (slave load rdb文件的时间) )
repl_backlog_size = 缓冲空间大小 * 2
如果主库每秒写入 2000 个操作,每个操作的大小为 2KB,
网络每秒能传输 1000 个操作,那么有1000个操作需要缓冲起来,这就至少需要 2MB 的缓冲空间。
否则,新写的命令就会覆盖掉旧操作了。为了应对可能的突发压力,把 repl_backlog_size 设为 4MB
三 Sentinel
Redis从2.8开始正式提供了Sentinel(哨兵)来解决主从切换问题;根据投票数自动将从库转换为主库, 类似zookeeper集群, 集群高可用心脏。
- Sentinel 负责持续健康主节点的健康, 客户端连接集群时,会首先连接Sentinel,
- 通过sentinel来寻址主节点,然后再连接主节点进行数据交互, 当主down了, 客户端重新向sentinel寻址, 完成主备切换,主修复后加入集群会成为从节点
- 如果主从延迟较大,sentinel无法保证切换时 主从数据状态一致,但会尽可能通过配置参数来保证一致性
min-slaves-to-wirte 1 # 至少一个从节点在进行正常复制, 否则停止对外服务, 不可用
min-slaves-max-lag 10 # 10s内没有收到slave节点心跳,意味着slave节点不正常
Sentinel的使用
from redis.sentinel import Sentinel
sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
master = sentinel.master_for('mymaster', socket_timeout=0.1)
slave = sentinel.slave_for ('mymaster', socket_timeout=0.1)
master.set('k1','v1')
slave.get('k1')
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行
其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例.
哨兵作用
- 1通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 2当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。
当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线
哨兵配置 & 启动
Redis-sentinel /myredis/sentinel.conf
# Example sentinel.conf
port 26379 # 哨兵sentinel实例运行的端口 默认26379
dir /tmp # 哨兵sentinel的工作目录
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
#这个数字越小,完成failover所需的时间就越长,
#但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
#可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.11.103:26379");
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等
等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。
#调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master
地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
#!/bin/bash
# @date: 2021-07-02
# @author: xxx
# @parm : null
# @desc: save master_repl_offset by mins
echo "start!"
for((i=6379; i<6387; i++))
do
redis-cli -p $i info replication|grep master_repl_offset|sed 's/:/|/g'|awk '{print strftime("%Y-%m-%d %H:%M:%S"),$0}'|sed 's/ /|/g'|sed 's/|/ /'|sed 's/^/'$i\|'/' >> /home/scripts/redis/redisParm.csv
echo "port $i save successful!"
done
echo "end!"
create table sor.redisparam_info
( port character varying( 50 ), evt_timestamp timestamp( 0 ) without time zone,
param character varying( 200 ),param_value character varying( 200 ),
CONSTRAINT redisparam_info_pkey PRIMARY KEY (evt_timestamp,port))
DISTRIBUTED by( evt_timestamp )
partition by range( evt_timestamp )( partition p202104 start( '2021-04-01'::date )
end( '2021-04-30'::date ), partition p202105 start( '2021-05-01'::date )
end( '2021-05-31'::date ), partition p202106 start( '2021-06-01'::date )
end( '2021-06-30'::date ), partition p202107 start( '2021-07-01'::date )
end( '2021-07-31'::date ), partition p202108 start( '2021-08-01'::date )
end( '2021-08-31'::date ), partition p202109 start( '2021-09-01'::date )
end( '2021-09-30'::date ), partition p202110 start( '2021-10-01'::date )
end( '2021-10-31'::date ), partition p202111 start( '2021-11-01'::date )
end( '2021-11-30'::date ), partition p202112 start( '2021-12-01'::date )
end( '2021-12-31'::date ));
drop external table ext_redisparam_info
CREATE EXTERNAL TABLE ext_redisparam_info (like sor.redisparam_info) LOCATION (
'gpfdist://******:8100/redisParm.csv'
) FORMAT 'text' (delimiter E'|' null E'\\N' escape E'\\')
SEGMENT REJECT LIMIT 1000 ROWS
select *,round(t.diff/1024.0,2) as kb,round(t.diff/1024.0/1024.0,2) mb
from( select *, param_value::bigint -( lead( param_value::bigint ) over(
order by evt_timestamp desc )) as diff
from sor.redisparam_info
where port = '6384'
) t
- 点赞
- 收藏
- 关注作者
评论(0)