1、介绍Redis6.0线程模型
1.1、流程
- 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
- 主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
- 主线程阻塞等待 IO 线程读取 socket 完毕
- 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行
- 主线程阻塞等待 IO 线程将数据回写 socket 完毕
- 解除绑定,清空等待队列
1.2、特点
- IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
- IO 线程只负责读写 socket 解析命令,不负责命令处理
1.3、线程安全问题(*)
Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。
1.4、什么是非阻塞IO
Redis客户端对服务端的调用分为发送命令,执行命令,返回结果三个过程。单线程如何高效的处理并发的网络请求,redis使用的是基于react模式(反应器模式,当检测到一个新的事件,将其发送给相应的Handler去处理)开发的网络事件处理器被称为文件事件处理器。
1.4.1、文件事件处理器
(1)组成结构
- 多个套接字
- IO多路复用程序
- 文件事件分派器
- 事件处理器
(2)原理
- 它使用IO多路复用程序,同时监听多个套接字,并根据套接字目前执行的任务,为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
- 尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字:当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。
1.4.2、文件事件
文件事件的类型分为可读事件(connect read close)
和可写事件(write)
,如果一个套接字同时产生了这两种事件,那么服务器将先读套接字,后写套接字。Redis为文件事件编写了多个处理器,分别为socket关联连接、命令请求、命令回复处理器等。
1.5、IO多路复用
多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求,且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
1.6、Redis多线程模型缺陷
Redis 多线程网络模型实际上并不是一个标准的 Multi-Reactors/Master-Workers模型。Redis 的多线程方案中,I/O 线程任务仅仅是通过 socket 读取客户端请求命令并解析,却没有真正去执行命令。所有客户端命令最后还需要回到主线程去执行,因此对多核的利用率并不算高,而且每次主线程都必须在分配完任务之后忙轮询等待所有 I/O 线程完成任务之后才能继续执行其他逻辑。
在我看来,Redis 目前的多线程方案更像是一个折中的选择:既保持了原系统的兼容性,又能利用多核提升 I/O 性能。