Redis设计原理之复制,Sentinel,redis集群(四)

版权声明:本文为博主原创文章,转载需注明出处. https://blog.csdn.net/piaoslowly/article/details/83339812

Redis设计原理之复制,Sentinel,redis集群(四)

复制

用户可以通过slaveof让B服务器去复制A服务器里面的数据.我们称B为从服务器,A为主服务器.也就是主从复制了.从作为主服务器的备份服务器.

127.0.0.1:7001> slaveof 127.0.0.1 7000 //将7001这个节点设置为7000这个节点的从服务器

当把7001这个节点设置为7000这个节点的从服务器后,实战中,看到的现象:

  1. 从服务器7001节点发送信息给7000主服务器.
  2. 主服务器收到从服务器请求后做asks答应. 这个时候主从建立关系喽.
  3. 主服务器发起bgsave备份.备份完成后发送RDB文件给从服务器.
  4. 从服务器接收到主服务器的RDB文件.
  5. 从服务器先清空自己服务器的数据.
  6. 从服务器载入主服务器的RDB文件到内存中.
  7. 主从同步完成.
127.0.0.1:7001> set key2 233
(error) READONLY You can't write against a read only slave.

7001设置为从服务器之后,写命令将会被拒绝

旧复制的实现原理

  1. 从服务器向主服务器发送sync命令
  2. 主服务器收到sync命令,开始在执行bgsave命令,在后台生成一个RDB文件,并使用缓冲区来记录从现在开始执行的所有写命令.
  3. 当主服务器的bgsave命令执行完毕后,主服务器会把RDB文件发送给从服务器.从服务器收到RDB文件后开始载入RDB文件,并更新自己的状态告知主服务器.
  4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器接受命令执行命令,同样更新自己的状态给主服务器.

命令传播机制

当从服务器备份完这个主服务器的数据后,主从达到了一致;但之后主服务器做了写操作,这个时候主从服务器又不一致了,这个时候主服务器会把写操作传播给从服务器.让从服务器再次执行一次这个命令.这样就又达到了主从一致了.

正常情况下:只有在第一次主从时才会整个载入RDB文件,之后都是通过命令传播机制来主从一致性的.

旧版本缺点

复制的两种情况:

  • 初次复制:从服务器以前没有复制过任何服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同.
  • 断线后重复复制:处于命令传播阶段的主从服务器因为网络原因而中断了复制,但从服务器通过自动重连接上了主服务器,并继续复制主服务器.

对于初次复制来说,主从都是一样的.不一样的事断线后重复复制.

旧版本的断线后发现主从不一致,会直接发送sync命令,和初次复制一样的操作.重新从主服务器那里备份一个最新的RDB文件,然后从服务器重新载入这个最新的RDB文件.这样就对主服务器从服务器都是非常的耗时.

注意:从服务器载入RDB文件或者主服务器重启时载入RDB文件,这个时间段Redis服务器是不能提供服务的.

新版本的复制功能(2.8版本后)

新版本使用psync来替代之前的sync命令.

pync命令具有完整重同步和部分重同步两种模式

  • 完整重同步:和旧版本的初次复制一样.载入整个RDB文件
  • 部分重同步:主要是解决断线后重连接,仅执行断线后的命令而不是重新执行整个RDB文件.

部分重同步的实现

部分重同步功能主要有三部分组成:

  • 主服务器和从服务器的复制偏移量
  • 主服务器的复制积压缓冲区
  • 服务器运行的ID

复制偏量

执行复制的主从双方,都分别维护一个复制偏移量.

  1. 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N.
  2. 从服务器每次接受到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N.

通过主从服务器的偏移量来对比主从是否一致,就变得很简单,如果偏移量相同,则主从服务一致,如果不是则不一致.

复制积压缓冲区

复制积压缓冲区是由主服务器维护的一个固定长度先进先出队列,默认大小为1MB.

当主从服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令写入到复制积压缓冲区里面.

复制积压缓冲区大小计算:2* second* write_size_per_second;
second:主从服务器断开时长.
write_size_per_second: 每秒有多少写数据.

服务器运行ID.

除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行ID.

  • 每个redis服务器,无论主从都有一个自己的运行ID.
  • 运行id在服务器启动时自动生成,有40个随机的16进制字符组成.

当从服务器对主服务器进行初次复制时,主服务器会将自己的运行id传送给从服务器,而从服务器会保存主服务的id.
断线重连后可以根据该id进行比对,如果相同则说明断线重连后是同一个主服务器,执行部分重同步操作;如果不等,则说明主服务器已经更换为其他服务器了,则执行完整重同步操作.

心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令,用来确认主从是否保持一致.
发送replconf ack <replication_offset>(偏移量),这个命令主要是三个作用:

  • 检测主服务器的网络状态
  • 满足实现min-slaves选项.
  • 检测命令丢失

min-slaves

redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令.
如:
min-slaves-to-write 3
min-slaves-max-lag 10

从服务器少于3个,或者三个从服务器的延迟值都大于或者等于10s,主服务器将拒绝执行写命令.

Sentinel

前面讲了主从复制.但是如果主挂了,从怎么办呢?
哨兵就是为了解决高可用性而来的,如果主挂了,那么从应该被升级为主服务器继续对外提供服务,这样才能达到高可用嘛.所以在配置哨兵模式下,可以配置一主多从.当一个主挂了,哨兵会从多个从服务器中挑选一个服务器为主服务继续对外提供服务.而剩下的从则继续为新主服务器的从服务器.

Sentinel本质上只是一个运行在特殊模式下的redis服务器,所以启动sentinel的第一步,就是初始化一个普通的redis服务器.只不过sentinel模式下,不会载入RDB或者AOF文件,还有命令,事务等不可用而已.

哨兵是Redis高可用性解决方案:由一个或多个sentinel实力组成的sentinel系统.可以监视任意多个主服务器.

当server1的下线时长超过用户设定的下线时间是,Sentinel系统就会对server1执行故障转移操作.

  1. sentinel系统会挑选server1属下其中一个从服务器升级为主服务器.
  2. sentinel系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移完毕.
  3. sentinel还会继续监视已下线的server1,如果server1重新上线,它将被设置为新主服务的从服务器.

结构

struct sentinelState{
	//用于实现故障转移
	uint64_t current_epoch;
	//保存了所有被这个sentinel键时的主服务器
	//字典的键是被监视主服务器的名字
	//value为sentinelRedisInstance实例 
	dict *masters;
	//是否进入tilt模式?
	int tilt;
	//目前正在执行的脚本数量
	int running_scripts;
	//进入tilt的时间
	mstime_t tilt_start_time;
	//进入tilt模式的时间
	mstime_t previous_time;
	//一个FIFO队列,包含了所有需要执行的用户脚本
	list *scripts_queue;
	
}sentinel

Sentinel配置文件

//监控了master1
sentinel monitor master1 127.0.0.1 6379 2
sentinel config-epoch master1 1

//监控了master2
sentinel monitor master2 127.0.0.1 12345 5
sentinel config-epoch master2 1

port 17000


注意:这里一个哨兵监控了2个master.但是两个master之间是没有任何关系的.互不影响的.只是说明一个哨兵可以监控多个主服务器而已.多个主服务器是互相独立的,没有任何关联关系.不然就变成集群去了.哈哈

创建连向主服务器的网络连接

初始化sentinel的最后一步就是创建连向主服务器的网络连接,sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并获取相关信息.
sentinel会创建两个连向主服务器的异步网络连接:

  • 一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复.
  • 另一个是订阅连接,这个连接专门用于订阅主服务器的__sentinel__:hello频道.

获取服主服务器信息

sentinel默认会以每10s一次的频率,通过命令连接向被监视的主服务器发送info命令,并通过分析info命令的回复来获取主服务器的当前信息(里面包含了主服务器有多少个从服务器等信息).


master里面包含了三个从服务器的信息.

检测下线状态

主动下线

在默认的情况下:sentinel会以每秒一次的频率向所有与它连接的实例(包括主从服务器,其他sentinel在内)发送ping命令 并通过实例返回平命令回复来判断实例是否在线.
sentinel配置文件中的down-after-milliseconds选项指定了sentinel判断实例进入主观下线所需要的时间长度.如果在该时间内没有响应则认为下线.

客观下线

当一个sentinel将一个主服务器判断为主观下线后,为了确认这个主服务器是否真的才下线了,它会向同样监视这一主服务器的其他sentinel询问,看它们是否也认为主服务器已经进入下线状态.当从其他sentinel那里接受到足够数量的已下线判断之后,并对主服务器执行故障转移操作.

sentinel monitor master1 127.0.0.1 6379 2
2表示两个总共有两个sentinel认为主服务器已经进入下线状态了,那么当前sentinel就将主服务器判断为客观下线.如果配置为5,则需要5个都认为它是下线状态,sentinel才会认为它客观下线.

选举领头Sentinel

当一个主服务器被判断为客观下线时,sentinel会进行协商,选出一个领头sentinel,并由领头sentinel对下线主服务器进行故障转移.

  • 所有在线的sentinel都有资格参选领头sentinel.
  • 每次选举之后,sentinel配置的current_epoch这个参数都会加1.
  • 发现主服务器被客观下线后,sentinel都会要求其他sentinel将自己设置为局部领头leader.
  • 选举中先获得超过半数以上的sentinel将被选举为leader.
  • 只能选举出一个leader出来.
  • 如果在规定时间内没有选举出leader,则再次进行选举.

故障转移

在选举产生出领头sentinel之后,领头sentinel将对一下线的主服务器执行故障转移操作.

  • 在从服务器里面挑选一个出来,作为主服务器
  • 其他从服务器改为复制新的主服务器.(将会重新倒入新主服务器的RDB文件)
  • 如果之前下线的老主服务器重新回复上线,则设置为新主服务器的从服务器.

如何选举主服务器:

  1. 删除列表中所有处于下线或者断线状态的从从服务器.
  2. 删除最近5s内没有回复过领头sentinel的info命令的从服务器.
  3. 删除所有已下线的主服务器.
  4. 领头sentinel将根据从服务器的优先级,挑选出优先级最高的一个作为主服务器.如果有多个优先级相同,则根据服务器的id最小的那个.

集群

前面哨兵模式提供了高可用性,但是始终只有一个master最为写redis,并不能做到高性能模式. 而redis的集群模式则是为了高性能而来的.

redis集群是redis提供分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能.

节点

一个Redis集群通常由多个节点组成,在刚开始各个节点都是独立的,将各个独立的节点连接起来就构成了一个包含多个基点的集群了.

命令如下:cluster meet <ip> <port>

假设现在由三个节点:7000,7001,70002.

$ redis-cli -c -p 7000 //通过-c命令,进入7000节点的集群模式
127.0.0.1:7000> cluster nodes //查看集群节点数
23rwasdfadr2234rqwasdzf123453wsdxf31 :0 myself,master - 0 0 0 connnected  //打印的结果可以看出,目前7000只包含自己一个集群节点.
127.0.0.1:7000> cluster meet 127.0.0.1 7001  //将7001节点添加到7000节点的集群中去.
127.0.0.1:7000> cluster meet 127.0.0.1 7002  //将7002节点添加到7000节点的集群中去.

集群数据结构


typedef stuct clusterState{
	
	//指向当前节点的指针
	clusterNode *myself;
	
	//集群当前的配置元,用于实现故障转移
	uint64_t currentEpoch;
	
	//集群当前的状态,下线还是在线
	int state;
	
	//集群中至少处理着一个槽节点的数量
	int size;
	
	//集群节点名单
	dict *nodes;
	
	//记录槽指派信息
	clusterNode *slots[16384];
	
}clusterState

 struct clusterNode{
   //创建节点的时间
 	mstime_t ctime;
 	
 	//节点的名字,有40个十六进制字符组成
 	char name[REDIS_CLUSTER_NAMELEN];
 	//节点标示
 	//主节点从节点,以及上线或者下线状态
 	int flags;
 	
 	//节点当前的配置元,用于故障转移
 	uint64_t configEpoch;
 	
 	//节点的IP地址
 	char ip[REDIS_IP_STR_LEN];
 	
 	//节点的端口号
 	int port;
 	
 	//保存连接节点所需要的相关信息,链表
 	clusterLink *link;
 	
 	unsigned char slots[16384/8];
	int numslots;
 }
 
 typedef struct clusterLink{
    //连接的创建时间
    mstime_t ctime;
    //tcp套接字描述符
    int fd;
    //输出缓冲区,保存着等待发送给其他节点的消息
    sds sndbuf;
    //输入缓冲区,保存着从其他节点接收到的消息
    sds rcvbuf;
    //与这个连接相关链的节点,如果没有的话就为null
    struct clusterNode *node;
 }clusterLink;
 
 

槽指派

redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16384个槽,数据库中的每个键都属于这16384中的一个.

当数据库中的16384个槽都有节点在处理时,那么集群处于上线状态,相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态.

就是说:集群中,任意一个节点挂了,都会导致整个集群处于下线状态.

//记录节点的槽指派信息
struct clusterNode{
	unsigned char slots[16384/8];
	int numslots;
} 

slots属性是一个二进制数据组,这个数组的长度为16384/8=2048个字节.共包含16384个二进制位.

传播节点的槽指派信息

一个节点除了记录自己处理的槽节点,还会将自己的信息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽.


本图中可以看出数组项slots[0]至slots[5000]的指针都指向了7000节点.表示0至5000槽都被指派给了节点7000.其他同理.

在集群中执行命令

在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了.

计算键属于哪个槽

计算key的算法: CRC16(key)&16383

CRC16(key)语句用于计算键key的CRC-1校验和.

节点数据库的实现

集群节点只能使用0号数据库,而单机则没有这一限制.

重新分片

Redis集群的重新分片操作可以让任意数量已经指派给某个节点的槽改到其他节点中去.并且相关槽所属的键值对也会从源节点转移到目标节点.

重新分片可以在线进行,重新分片过程中,集群不需要下线.并且源节点和目标节点都可以继续处理请求.

重新分片的实现原理

Redis集群的重新分片操作是有Redis的集群管理软件redis-trib负责执行的.

重新分片过程中,怎么处理请求呢?

复制与故障转移

上面说了集群一多个主节点平分了16384个槽节点,这样增加了Redis高写性能.假如其中一个主节点挂了怎么办呢?
集群模式=分片+哨兵+复制模式.

  • 有了复制模式,集群中的每个主节点都可以有自己的从节点,从节点只负责复制自己对应的主节点.
  • 有了Sentinel模式,集群中,当其中一个主节点挂了,主节点下面的从节点会被升级为主节点,继续提供服务.
  • 有了分片模式,集群中,可以有多个主节点一起提供写服务器.

在集群模式(7000,7001,7002),现在7000要加入2台从服务器7004,7005.当设置7004为7000的从节点并还是复制7000数据的时候,会通过消息的形式发送给集群中的其他节点,最终集群中的所有节点都会知道7004从节点正在复制7000这个主节点.
从服务器发现自己复制的主服务被标记会下线状态,发送一条广播给所有主服务器节点,由其他主服务节点发起投票,选举出一个新的主节点.选举出来的新主节点将通知集群中的其他主节点.告知对方自己已经成为新的主服务器了,新的主服务器将负责原先下线的主服务器的槽点.

集群模式下(7000,7001,7002),每个主服务除了提供读写服务外,还提供了一个Sentinel服务,每个主服务之间通过ping来互相确认对方是否在线状态,如果主服务器7000在规定时间内没有响应其他主服务器,7001,7002主服务器会先在自己内部状态中标记主服务器7000疑似下线了状态,然后发送广播给其他主服务器,一起投票之后,票数达到集群主节点半数以上将会把7000服务器标记会已下线状态.

故障转移这里,可以认为一个主节点就是一个sentinel服务.集群的故障转移操作和选举主节点操作都是一样的操作.
在集群模式中,各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息.就是说在整个集群大家庭里面,他们之间是能互相通信的,互相知道对方的存在的.

例如:主节点:7000(7000的从节点:7004,7005),7001,7002

7000节点下线后:7004被选举为主节点将负责7000节点的槽节点,并且更改7005节点的复制新主节点7004.

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/83339812