Redis核心技术与实战-蒋德钧

  • 01 基本架构:一个键值数据库包含什么 _2022年10月9日 完成
  • 03 高性能IO模型:为什么单线程Redis能那么快?_2022年10月13日 完成
  • 08-哨兵集群:哨兵挂了,主从库还能切换吗?

01 基本架构:一个键值数据库包含什么

关注 Redis 的数据模型和操作接口,Redis能够在实际业务场景中得到广泛的应用,就是得益于支持多样化类型的value(包括String、hash、list、set)。

从使用的角度来说,不同value类型的实现,不仅可以支撑不同业务的数据需求,而且也隐含着不同数据结构在性能、空间效率等方面的差异,从而导致不同的value操作之间存在着差异。

可以对数据做什么操作?

常见的就是:put、get、delete、SCAN操作即根据一段key的范围返回相应的value值。

内存型数据库:保存在内存的好处是读写很快,毕竟内存的访问速度一般都在百ns级别,常见场景就是缓存场景下的数据需要能快速访问但允许丢失

一个键值数据库包括了访问框架、索引模块、操作模块和存储模块

fig1

访问模式:一种是通过函数库调用的方式供外部应用使用,比如,上图中的libsimplekv.so,就是以动态链接库的形式链接到我们自己的程序中,提供键值存储功能;另一种是通过网络框架以Socket通信的形式对外提供键值对操作,

索引的作用是让键值数据库根据key找到相应value的存储位置,进而执行操作。Memcached和Redis采用哈希表作为key-value索引。一般而言,内存键值数据库(例如Redis)采用哈希表作为索引,很大一部分原因在于,其键值数据基本都是保存在内存中的,而内存的高性能随机访问特性可以很好地与哈希表O(1)的操作复杂度相匹配。

Fig2

给你留个小问题:和你了解的Redis相比,你觉得,SimpleKV里面还缺少什么功能组件或模块吗?

03-高性能IO模型:为什么单线程Redis能那么快?

Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

多线程提高吞吐量,但是并不是越多线程越好,比如多个线程维护一个 list 需要额外开销处理并发,还要考虑锁的粒度
Fig1
(高性能):Redis的大部分操作在内存上完成,再加上它采用了高效的数据结构

(高吞吐率):Redis采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率

基本IO模型与阻塞点

  • 阻塞式IO
  • 非阻塞式 IO,Socket 可以设置非阻塞
    fig

基于多路复用的高性能I/O模型

在Redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给Redis线程处理,这就实现了一个Redis线程处理多个IO流的效果。

Fig7
为了在请求到达时能通知到Redis线程,select/epoll提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。

08-哨兵集群:哨兵挂了,主从库还能切换吗?

sentinal机制,sentinal主要负责就是监测、选主、通知

配置

sentinel monitor <master-name> <ip> <redis-port> <quorum> 

基于pub/sub机制的哨兵集群组成

哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如说发布它自己的连接信息(IP和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息。当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的IP地址和端口。

Redis会以频道的形式,对这些消息进行分门别类的管理。所谓的频道,实际上就是消息的类别.只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。

sentinal cluster 的结构图

fig1
哨兵建立连接形成集群,还要和从库建立连接。因为,在哨兵的监控任务中,它需要对主从库都进行心跳判断,而且在主从库切换完成后,它还需要通知从库,让它们和新主库进行同步。

哨兵获取从库的信息,是通过主库来的,向主库发送 INFO 命令,主库返回从库连接信息。哨兵还需要完成把新主库的信息告诉客户端这个任务。
fig2

基于pub/sub机制的客户端事件通知

Q:如何在客户端通过监控了解哨兵进行主从切换的过程呢?比如说,主从切换进行到哪一步了?
从本质上说,哨兵就是一个运行在特定模式下的Redis实例,只不过它并不服务请求操作,只是完成监控、选主和通知的任务。所以,每个哨兵实例也提供pub/sub机制,客户端可以从哨兵订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件。

redis 中重要频道
fig3

SUBSCRIBE +odown # 订阅“所有实例进入客观下线状态的事件”
PSUBSCRIBE  *    # PSUBSCRIBE  *
switch-master <master name> <oldip> <oldport> <newip> <newport> # 哨兵把新主库选择出来后

由哪个哨兵执行主从切换?

当一个哨兵发现主库下线后“主观下线”,像其他哨兵发送 is-master-down-by-addr 命令,其他哨兵根据自己和主库的连接情况,做出Y或N的响应,Y相当于赞成票,N相当于反对票。

一个哨兵获得了仲裁所需的赞成票数后,就可以标记主库为“客观下线”。这个所需的赞成票数是通过哨兵配置文件中的quorum配置项设定的。超过quorum 配置,就可以标记主库“客观下线”。此时,这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。这个投票过程称为“Leader选举”

fig4

Leader 选举,当主库客观下线后,哨兵集群中没有选出Leader,那么这轮投票就不会产生Leader。哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的2倍),再重新选举。这是因为,哨兵集群能够进行成功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞,就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进行投票选举,成功的概率就会增加。

需要注意的是,如果哨兵集群只有2个实例,此时,一个哨兵要想成为Leader,必须获得2票,而不是1票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置3个哨兵实例。这一点很重要,你在实际应用时可不能忽略了。

小结

通常,我们在解决一个系统问题的时候,会引入一个新机制,或者设计一层新功能,就像我们在这两节课学习的内容:为了实现主从切换,我们引入了哨兵;为了避免单个哨兵故障后无法进行主从切换,以及为了减少误判率,又引入了哨兵集群;哨兵集群又需要有一些机制来支撑它的正常运行。

重要经验:要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值down-after-milliseconds

Question

Q:假设有一个Redis集群,是“一主四从”,同时配置了包含5个哨兵实例的集群,quorum值设为2。在运行过程中,如果有3个哨兵实例都发生故障了,此时,Redis主库如果有故障,还能正确地判断主库“客观下线”吗?如果可以的话,还能进行主从库自动切换吗?此外,哨兵实例是不是越多越好呢,如果同时调大down-after-milliseconds值,对减少误判是不是也有好处呢?

A:

Redis 1主4从,5个哨兵,哨兵配置quorum为2,如果3个哨兵故障,当主库宕机时,哨兵能否判断主库“客观下线”?能否自动切换?

1、哨兵集群可以判定主库“主观下线”。由于quorum=2,所以当一个哨兵判断主库“主观下线”后,询问另外一个哨兵后也会得到同样的结果,2个哨兵都判定“主观下线”,达到了quorum的值,因此,哨兵集群可以判定主库为“客观下线”。

2、但哨兵不能完成主从切换。哨兵标记主库“客观下线后”,在选举“哨兵领导者”时,一个哨兵必须拿到超过多数的选票(5/2+1=3票)。但目前只有2个哨兵活着,无论怎么投票,一个哨兵最多只能拿到2票,永远无法达到多数选票的结果。

但是投票选举过程的细节并不是大家认为的:每个哨兵各自1票,这个情况是不一定的。下面具体说一下:

场景a:哨兵A先判定主库“主观下线”,然后马上询问哨兵B(注意,此时哨兵B只是被动接受询问,并没有去询问哨兵A,也就是它还没有进入判定“客观下线”的流程),哨兵B回复主库已“主观下线”,达到quorum=2后哨兵A此时可以判定主库“客观下线”。此时,哨兵A马上可以向其他哨兵发起成为“哨兵领导者”的投票,哨兵B收到投票请求后,由于自己还没有询问哨兵A进入判定“客观下线”的流程,所以哨兵B是可以给哨兵A投票确认的,这样哨兵A就已经拿到2票了。等稍后哨兵B也判定“主观下线”后想成为领导者时,因为它已经给别人投过票了,所以这一轮自己就不能再成为领导者了。

场景b:哨兵A和哨兵B同时判定主库“主观下线”,然后同时询问对方后都得到可以“客观下线”的结论,此时它们各自给自己投上1票后,然后向其他哨兵发起投票请求,但是因为各自都给自己投过票了,因此各自都拒绝了对方的投票请求,这样2个哨兵各自持有1票。

场景a是1个哨兵拿到2票,场景b是2个哨兵各自有1票,这2种情况都不满足大多数选票(3票)的结果,因此无法完成主从切换。

经过测试发现,场景b发生的概率非常小,只有2个哨兵同时进入判定“主观下线”的流程时才可以发生。我测试几次后发现,都是复现的场景a。

哨兵实例是不是越多越好?

并不是,我们也看到了,哨兵在判定“主观下线”和选举“哨兵领导者”时,都需要和其他节点进行通信,交换信息,哨兵实例越多,通信的次数也就越多,而且部署多个哨兵时,会分布在不同机器上,节点越多带来的机器故障风险也会越大,这些问题都会影响到哨兵的通信和选举,出问题时也就意味着选举时间会变长,切换主从的时间变久。

调大down-after-milliseconds值,对减少误判是不是有好处?

是有好处的,适当调大down-after-milliseconds值,当哨兵与主库之间网络存在短时波动时,可以降低误判的概率。但是调大down-after-milliseconds值也意味着主从切换的时间会变长,对业务的影响时间越久,我们需要根据实际场景进行权衡,设置合理的阈值

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐