Redis 设计与实现读书笔记(三).md
Contents
终于来到我最喜欢的复制哨兵集群部分
复制(换句话说就是主从服务器)
用户可以通过SLACEOF命令,让一个服务器去复制另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器被称为从服务器(slave).
旧版复制功能的实现
Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作:
- 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
- 命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态。
同步
从服务器对主服务器的同步操作需要通过向主服务器发起SYNC命令来完成,以下是SYNC命令的执行步骤:
1)从服务器向主服务器发送SYNC命令。
2)收到SYNC命令的主服务器执行BGSAVE,在后台生成一个RDB文件,并使用一个缓冲区记录从现在执行的所有写命令。
3)当主服务器的BGSAVE命令执行完毕,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
4)住服务将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据当前所处的状态。
按我自己来看,就是生成一份快照,然后再把生成快照期间产生的发过来。乍一看还是非常合理的呀。
命令传播
在同步操作执行完毕之后,主从服务两者的数据库将达到一致状态,但这种一致并不是一成不变的,每当主服务器执行客户端发送的写命令时,主服务器的数据库就有可能会被修改,并导致主从服务器状态不再一致。
就是同步完后又修改了,然后必然不一致。
为了让主从服务器再次回到一致状态,主服务器需要对从服务执行命令传播操作:主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了同样的写命令之后,主从服务器再次回到一致状态。
缺陷:
从服务器对主服务器的复制分为:
1.初次复制.
2.断线后重复制.
对于初次复制,旧版复制功能能很好地完成任务,但断线后重复制,能完成任务,但是效率低。
就是他的断线重复制没有任何的记忆。只能全部重头来一遍。
新版复制功能的实现
Redis从2.8版本开始,使用PSYNC命令代替SYNC命令来执行复制时的同步操作。
PSYNC具有完整重同步和部分重同步两种模式
- 完整重同步用于初次复制,和SYNC是一样的。
- 部分重复制用于断线后重复制,当从服务器重连后,如果条件允许,主服务器将主从服务器断线期间写命令发给从服务器。
举个不太恰当的例子,相当于加了一个断点续存的功能。
部分重同步功能由以下三个部分构成:
- 主服务器的复制偏移量和从服务的复制偏移量。
- 主服务的复制积压区。
- 服务器的运行ID。
1.主服务器每次向服务传播N个字节数据,将自己的复制偏移量加上N。
2.从服务器每次收到主服务传播来的n个字节的数据,将自己的复制偏移量加上N。
有点那种TCP的序号的意思?
复制积压缓冲区由主服务器维护的一个固定长度先进先出队列。
当主服务器进行命令传播时,不仅会将写命令发送到所有从服务器,还会写到复制积压缓冲区。
保存的是偏移量和对应字节。
这个可以,只积压一部分。太多直接完整重同步了。
还有一个运行ID,就是检验是不是对应主服务器罢了。
这个业务逻辑还是非常清晰的。
哨兵(Sentinel)
Sentinel是Redis的高可用性(highg availability)(CAP)?解决方案:由一个或多个Sentinel组成的哨兵系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器下线状态时,自动将主武器属下的某个从服务器升级为新的主服务器。
当主服务的下线时长超过用户设定的下线时长上限时,Sentinel系统就会对主服务器进行故障转移操作:
- 挑选一个从服务器,升级为新的主服务器。
- 之后sentinel会向之前主服务器下所有服务器发送新的复制指令,让他们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。
- sentinel还会继续监视已下线的主服务器,当它重新上线时,让它成为新的主服务器的从服务器。
有点虎符的意思,虎符在谁手上听谁的。
Sentinel本质上只是一个运行在特殊模式下的Redis服务器
突发奇想,sentinel挂了咋办
获取主服务器信息
sentinel默认会以每10秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过回复获取主服务器的当前信息。
从服务器同样也是10一次发送INFO命令
检测主观下线状态
默认情况下,sentinel会以每秒1次的频率想所有与它创建了命令连接的实例发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。
根据配置文件down_after-milliseconds 设为50000ms,如果主服务器连续50000mss都返回无效回复。
sentinel就会标记会主观下线。
就是不是一次就定生死,还是会给一点机会的。
检测客观下线状态
当sentinel将一个主服务器判断为主观下线,为了确认这个服务器是否真的下线,它会向同样监视这一主服务器的其他sentinel进行询问,当下线判断数量足够时,sentinel就会将服务器判定为客观下线,并对主服务器进行故障转移。
选举领头Sentinel:
由领头sentinel对下线主服务进行故障转移操作
选举办法。
- 所有在线的sentinel都有选为领头Sentinel的资格
- 每次进行领头选举之后,sentinel的配置纪元的值都会自增一次。一个计数器。
配置纪元相当于选举届数
- 在一个配置纪元里面,所有sentinel都有一个一个sentinel设为局部sentinel的机会。这次不可修改。
每个人都有选票
- 拉票命令sentinel-is-maser-down-by-addr 拉票命令
- 规矩是先到先得;后面的都会被拒绝。
- 如果有某人sentinel被半数以上选举,那就成为领头sentinel.(10个需要6个)
- 规定时间内,没选举处理。再在一段时间后再进行选举,直到选出来为止。
选择新主服务器:
删除下线或者断线的肯定要走
删除最近五秒没回复领头Sential INFO
删除与已下线主服务器连接断开超过,down_affter_milliseconds * 10 为了数据比较新
然后看优先级,优先级看完看偏移量,如果还不行看ID。
集群
Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。介绍集群的节点、槽指派、命令执行、重新分片、转向、故障转移、消息。
节点
Redis集群由多个节点组成。它们都处于一个只包含自己的集群当中,要组建集群,就要把独立的节点连接起来。
1 | cluster meet |
槽指派
Redis集群通过分片的方式保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),集群中的每个节点可以处理0个或16384个槽。
当数据库的16384个槽都有节点在处理时,集群处于上线状态。任意一个槽没有得到处理,集群处于下线状态。
有一致性哈希那味了
1 | cluster addslots 指派槽给节点 |
在集群中执行命令
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
- 如果键锁在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
- 如果没有指派给当前节点,节点向客户端返回一个moved错误,直营客户端redirect至正确的节点,并再次发送之前想要执行的命令。
计算key属于哪个槽算法:
1 | def slot_number(key): |
重新分片
将任意数量已经指派给某个节点的槽改为指派给另一个节点,相关的键值对也会转移。
不需要下线,源节点和目标节点都可以继续处理命令请求。
ASK错误:
命令要处理的数据库键恰好属于正在被迁移的槽:
- 先在自己数据库找,找到直接返回。
- 没找到,返回一个ask错误,指引客户端转向目标节点。
故障检测
集群中的每个节点都会定期地向集群中的其他节点发送ping消息,以检测对方是否在线,如果在规定时间内没有返回pong,那么标记为疑似下线。
这和之前哨兵不一样,他们是自己检举自己,哨兵是别人监管他们。那这么看好像哨兵效率更高一些。
如果在一个集群中,半数以上负责处理槽的主节点都把某个主节点x报告为疑似下线,那么这个主节点x被标记为已下线,将主节点x标记为已下线的节点会向集群广播一条关于主节点x的fail信息。所有收到这条fail消息的节点,都会标记x已下线。
选举新节点和哨兵选择新领头是一样的。