一.Reactor线程模型简介
Reactor模型都是 利用IO多路复用接收客户端请求。不同点在于接受请求后的处理。
1.1 单线程Reactor
从负责接收请求、读取请求到命令执行再到响应请求都是 单个线程执行
1.2 单线程 Reactor,工作者线程池模式
单个线程负责接收请求、读取请求和响应请求。 多线程执行计算。
1.3 R主从Reactor多线程模式线程模式
将Reactor分为mainReactor和subReactor。
- Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
- 当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
- SubReactor将连接加入到连接队列进行监听,并创建Handler进行各种事件处理
- 当有新的事件发生,SubReactor就会调用对应的Handler处理
- Handler通过read读取数据,分发给后面的worker线程处理
- worker线程池分配独立的worker线程进行处理,并返回结果
- handler收到响应结果后,再通过send将结果返回给client
- Reactor主线程可以对应多个Reactor子线程,即MainReactor可以关联多个SubReactor
二. redis单线程模型
Redis 基于 Reactor 模式开发了自己的网络事件处理器 - 文件事件处理器(file event handler,后文简称为 FEH),而该处理器又是单线程的,所以 redis 设计为单线程模型。
文件事件分派器从队列中接收 I/O 多路复用程序传来的socket,并根据socket产生的事件类型,调用相应的事件处理器。当上一个socket产生的事件被对应事件处理器执行完后,文件事件分派器才会向队列拉取下一个要处理的socket,保证socket操作一定不会并发的执行,保证线程安全。
Redis所谓的单线程并不是所有工作都是只有一个线程在执行,而是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理。
这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。由于Redis在处理命令的时候是单线程作业的,所以会有一个Socket队列,每一个到达的服务端命令来了之后都不会马上被执行,而是进入队列,然后被线程的事件分发器逐个执行。
文件事件处理器的几个组成部分:
IO多路复用
Redis 的 I/O 多路复用程序的所有功能都是通过包装常见的 select、epoll、evport 和 kqueue 这些 I/O 多路复用函数库实现的,不同系统不同函数。 liunx采用 epoll函数。
三. redis多线程模型
redis 6.0 引入多线程。
3.1. 6.0之前真的是单线程么
6.0之前单线程值的是处理客户端请求是单线程的。 但是对于持久化,主动同步,大key处理,数据过期等功能都是其他线程完成
3.2. 6.0 多线程含义
redis处理接受客户端请求后,可以简化为三部分操作:
- read 读区请求
- parse->resp>command excute 解析请求到命令执行
- write 相应请求
6.0之前,以上三个步骤依次执行。
6.0之后,read和write两部分支持多线程,第二部处理指令依然是单线程。
6.0版本优化之后,主线程和多线程网络IO的执行流程如下:
具体步骤如下:
- 主线程建立连接,并接受数据,并将获取的 socket 数据放入等待队列;
- 通过轮询的方式将 socket读取出来并分配给 IO 线程;
- 之后主线程保持阻塞,一直等到 IO 线程完成 socket 读取和解析;
- I/O 线程读取和解析完成之后,返回给主线程 ,主线程开始执行 Redis 命令;
- 执行完Redis命令后,主线程阻塞,直到IO 线程完成 结果回写到socket 的工作;
- 主线程清空已完成的队列,等待客户端新的请求。
本质上是将主线程 IO 读写的这个操作 独立出来,单独交给一个I/O线程组处理。
这样多个 socket 读写可以并行执行,整体效率也就提高了。同时注意 Redis 命令还是主线程串行
这里举个生活中的例子展示IO读写多线程如何提高效率的:
车站买票和上车,买票对应IO读写,上车对应redis操作
假设没有多线程,也就是相当于只有一个买票窗口,那么人太多,买票窗口就会聚集大堆人,一个人买到票才轮到下一个,买票时间随着人数的增多而增多,多线程就是多开几个窗口,解决买票排队情况,降低大家总的买票时间。可能你会问,上车不还是一个一个按顺序上吗,哪怕你全部人一下子买好票了,我上车的速度还是不变啊?对,但上车几乎是不花费时间的(基于内存),也就是不管你现在是1个人买票了,还是1000个人买票了,都可以秒上车,所以买票和上车的瓶颈在买票,提高买票效率则提高整体效率。
3.2.1 写操作支持多线程
可以通过如下参数配置多线程模型:
// 这里说有三个IO 线程,还有一个线程是main线程,main线程负责IO读写和命令执行操作
io‐threads 4
3.2.2 读操作支持多线程
// 将支持IO线程执行 读写任务。
io‐threads‐do‐reads yes
3.3. redis6.0之前为什么不支持多线程
- 使用 Redis 时,几乎不存在 CPU 成为瓶颈的情况, Redis 主要受限于内存和网络
- 使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗
3.4 Redis6.0 为什么要引入多线程呢?
- 可以充分利用服务器CPU的多核资源,而主线程明显只能利用一个
- 多线程任务可以分摊 Redis 同步 IO 读写负荷,降低耗时
Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,对于小数据包,Redis 服务器可以处理 80,000 到 100,000 QPS,这也是 Redis 处理的极限了,对于 80%的公司来说,单线程的 Redis 已经足够使用了。
随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的 QPS. 常见解决方案是分布式架构中对数据进行分区并采用多个服务器。这种方式: 维护代价大;某些命令失效;数据分区无法解决热点
读/写问题,数据偏斜,重新分配和放大/缩小变得更加复杂等等。
从 Redis 自身角度来说,因为读写网络的 read/write 系统调用占用了 Redis执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗. redis6.0充分利用多核cpu的能力分摊 Redis 同步 IO 读写负荷
作者:乔_帮_主
链接:https://www.jianshu.com/p/de6c41e05f65
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章: