Redis的线程模型

redis事件模型
redis是一个事件驱动的服务程序,在redis的服务程序中存在两种类型的事件,分别是文件事件和时间事件。
文件事件是对网络事件操作的统称,时间事件是redis中定时运行的任务或者是周期性的任务(如bgsave的执行时候 是由serverCron来完成的,serverCron 100ms 执行一次)。
Redis 基于Reactor 模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成分为4部分:多个套接字(socket)、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器对队列的消费是单线程的,所以Redis才叫单线程模型。
消息处理流程
文件事件处理器使用I/O多路复用程序来同时监听多个socket,并根据socket目前执行的任务来为socket关联不同的事件处理器。
当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。 这2步就是先建立关联,有事就来处理。
尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字:当上一个socket 产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O多路复用程序才会继续向文件事件分派器传送下一个socket。
I/O多路复用程序的实现
Redis的I/O多路复用程序的所有功能是通过包装select、epoll、evport和kqueue这些I/O多路复用函数库来实现的,每个I/O多路复用函数库在Redis源码中都对应一个单独的文件,比如ae_select.c、ae_epoll.c、ae_kqueue.c等。
因为Redis为每个I/O多路复用函数库都实现了相同的API,所以I/O多路复用程序的底层实现是可以互换的。具体IO的多路复用在其他里面总结吧,不然太乱了。
文件事件的类型
当socket变得可读时(比如客户端对redis执行write操作,或者close操作),或者有新的可以应答的sccket出现时(客户端对redis执行connect操作),socket就会产生一个AE_READABLE事件。
当socket变得可写的时候(客户端对redis执行read操作),socket会产生一个AE_WRITABLE事件。
IO多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件,要是一个socket同时产生了AE_READABLE和AE_WRITABLE两种事件,那么文件事件分派器优先处理AE_REABLE事件,然后才是AE_WRITABLE事件
文件事件分派器
当IO的多路复用监听到server socket 产生的事件之后,将事件压入队列中,文件事件分派器从队列中获取事件,然后根据对应的事件类型来调用事件中注册的不同处理器。
事件处理器
Redis为文件事件编写了多个处理器,这些事件处理器分别用于实现不同的网络通讯需求,常用的处理器如下:
为了对连接服务器的各个客户端进行应答, 服务器要为监听socket关联连接应答处理器。
为了接收客户端传来的命令请求, 服务器要为客户端socket关联命令请求处理器。
为了向客户端返回命令的执行结果, 服务器要为客户端socket关联命令回复处理器。
1 )连接应答处理器,这个处理器用于对连接服务器监听套接字的客户端进行应答。当Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,当有客户端用sys/socket.h/connect函数连接服务器监听套接字的时候, 套接字就会产生AE_READABLE 事件, 引发连接应答处理器执行, 并执行相应的套接字应答操作
2) 命令请求处理器:这个处理器负责从套接字中读入客户端发送的命令请求内容,
当一个客户端通过连接应答处理器成功连接到服务器之后, 服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生 AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作。在客户端连接服务器的整个过程中,服务器都会一直为客户端套接字的AE_READABLE事件关联命令请求处理器。
3)命令回复处理器:这个处理器负责将服务器执行命令后得到的命令回复通过套接字返回给客户端。当命令回复发送完毕之后, 服务器就会解除命令回复处理器与客户端套接字的 AE_WRITABLE 事件之间的关联。
tips: 事件处理器 还有 主从连接处理器,Ping/Pong处理器。
一次完整的客户端与服务器连接事件的流程:
1 > 连接应答器是在redis服务器进行初始化的时候,就已经和socket关联了。
2> 一个客户端来跟服务器发起连接,那么socket 将产生AE_READABLE 事件,触发连接应答处理器执行:
连接应答处理器会对客户端的连接请求进行应答, 然后创建客户端socket,以及客户端状态,并将客户端套接字的 AE_READABLE 事件与命令请求处理器进行关联,使得客户端可以向主服务器发送命令请求。
3> 客户端发送命令请求,那么客户端的socket将产生AE_READABLE事件,引发命令请求处理器执行,处理器读取客户端的命令内容, 然后传给相关程序去执行。
执行命令将产生相应的命令回复,为了将这些命令回复传送回客户端,服务器会将客户端socket的AE_WRITABLE事件与命令回复处理器进行关联:当客户端尝试读取命令回复的时候,客户端套接字将产生AE_WRITABLE事件, 触发命令回复处理器执行, 当命令回复处理器将命令回复全部写入到套接字之后, 服务器就会解除客户端套接字的AE_WRITABLE事件与命令回复处理器之间的关联。
Redis单线程模型效率为什么还这么高?
1 纯内存访问
2 IO的多路复用
3 单线程没有线程上下文切换
4 合理的数据编码
5 高效的数据结构
redis的限制是带宽和内存,不是CPU
redis6.0 引入了多线程

Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。

常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:
    • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

    • 使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:
    • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

    • 多线程任务可以分摊 Redis 同步 IO 读写负荷

详细内容可以看这篇:https://mp.weixin.qq.com/s/yqCPTU6NAIEpqbhW_JBZ9g

总结:
redis 线程模型基本包括文件事件模型和时间事件模型,文件事件模型主要处理网络的I/O事件。
文件事件处理器包括4部分,多个套接字(socket)、IO多路复用程序、文件事件分派器、事件处理器。
文件事件包括AE_READABLE事件和AE_WRITEABLE事件。
事件处理器有连接应答处理器、命令请求处理器、命令回复处理器。
事件处理器是用来处理文件事件的。
https://blog.csdn.net/qq_35524586/article/details/84671225

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注