Redis核心技术02-线程IO模型
# Redis核心技术02-线程IO模型
有一个经典问题:Redis单线程为什么还能这么快?
# 为什么使用单线程
首先Redis单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。
但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
# 多线程的开销
对于一个多线程的系统来说,在有合理的资源分配的情况下,可以增加系统中处理请求操作的资源实体,进而提升系统能够同时处理的请求数,即吞吐率。
但是当多个线程同时访问一个共享的数据区时,为了保证这个数据区的正确性,就需要有额外的机制进行保证,而这个额外的机制就会带来额外的开销。
如果没有精细设计并发访问控制,那么大部分线程也在等待获取访问共享资源的互斥锁,并行变串行。
# 单线程为什么快
一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。
# 非阻塞IO
当我们调用套接字的读写方法,默认是阻塞的。比如read方法,如果此刻一个字节都没有,线程就会卡着,直到新的数据来或者连接关闭,线程才能继续处理。相似的write方法,如果分配的写缓冲区已经满了,write方法就会阻塞。
非阻塞IO在套接字对象上提供了选项Non_Blocking,这样读写方法不会阻塞,而是能读多少读多少,能写多少写多少。
能读多少取决于内核为套接字分配的读缓冲区内部的数据字节数。
能写多少取决于内核为套接字分配的写缓冲区的空闲空间字节数。
# 事件轮询(多路复用)
通过非阻塞IO,线程在读写IO的时候就不会阻塞,但是存在一个问题。
当一个线程读写完,去做另一件事情了。这时数据来了,那么是不是读线程应该去读这部分数据了。同理写进程也是一样,上次因为缓冲区空间不够了没写完数据,等缓冲区有了空间了,写进程就应该去写了。对于这两个进程我们还需要做一件事情:那就是通知他们去读写。
在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
Redis网络框架调用epoll机制,让内核监听套接字。此时Redis线程不会阻塞在某一个监听或已连接套接字上。
为了在请求达到的时候通知Redis线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
一旦监听到FD上有请求发生,就会触发相应的事件。这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。这样一来, Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时, Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。
# 参考
Redis深入历险核心原理与应用实践