Redis基础篇(二)高性能IO模型

咱们常常听到说Redis是单线程的,也会有疑问:为何单线程的Redis能那么快?网络

这里要明白一点:Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其余功能,好比持久化、异步删除、集群数据同步等,都是由额外的线程执行的。数据结构

咱们知道多线程可以提高并发性能,那为何Redis会采用单线程,而非多线程?为何单线程能那么快?多线程

下面咱们就来学习一下Redis采用单线程的缘由。并发

为何采用单线程?

使用多线程,虽然能够增长系统吞吐率,或是增长系统扩展性,但一样会产生开销。异步

Redis的数据是在内存里的,是共享的,若是使用多线程就会引起共享资源的竞争,须要引入互斥锁来解决,使得并行变串行。最终系统吞吐率并无随着线程的增长而增长。socket

另外,多线程开发须要精细的设计,会增长系统的复杂度,下降代码的易调试性和可维护性。为了不这些问题,Redis采用单线程模式。函数

单线程Redis为何那么快?

一般来讲,单线程的处理能力比多线程要差不少,那Redis却能使用单线程模型达到每秒数十万级别的处理能力,这是为何呢?高并发

一方面,Redis大多数操做是在内存上完成的,而且采用高效的数据结构,例如哈希表和跳表。另外一方面,Redis采用了多路复用机制,使其在网络IO操做中能并发处理大量的客户端请求,实现高吞吐率。性能

在学习多路复用机制前,咱们要弄明白网络操做的基于IO模型和潜在的阻塞点。学习

基本IO模型与阻塞点

以Get请求为例,为了处理一个Get请求:

  1. 须要监听客户端请求(bind/listen)
  2. 和客户端创建链接(accept)
  3. 从socket中读取请求(recv)
  4. 解析客户端发送请求(parse)
  5. 根据请求类型读取键值数据(get)
  6. 最后给客户端返回结果,即向socket中写回数据(send)。

下图显示了这一过程,其中,bind/listen、accept、recv、parse和send属于网络IO处理,而get属性键值数据操做。

image

可是在这里的网络IO操做中,有潜在的阻塞点,分别是accept()和recv()。

  • 当Redis监听到一个客户端有链接请求,但一直未能成功创建起链接时,会阻塞在accept()
  • 当Redis经过recv()从一个客户端读取数据时,若是数据一直没有到达,Redis也会一直阻塞在recv()

这就致使Redis整个线程阻塞,没法处理其余客户端请求,效率很低。不过,幸运的是,socket网络模型自己支持非阻塞模式。

非阻塞模式

Socket网络模型能够设置非阻塞模式。

image

这样能保证Redis线程既不会像基本IO模型中一直在阻塞点等待,也不会致使Redis没法处理实际到达的链接请求或数据。

下面就到多路复用机制登场了。

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

Linux的IO多路复用机制是指一个线程处理多个IO流,也就是select/epoll机制。

在Redis运行单线程下,该机制容许内核中,同时存在多个监听套接字和已链接套接字。

基于多路复用的Redis IO模型

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

回调机制的工做流程:

  1. select/epoll一旦临听到FD上有请求到达,就会触发相应的事件,并放进一个事件队列中。
  2. Redis单线程对事件队列进行处理便可,无需一直轮询是否有请求发生,避免CPU资源浪费。

由于Redis一直在对事件队列进行处理,因此能及时响应客户端请求,提高Redis的响应性能。

不过,须要注意的是,在不一样的操做系统上,多路复用机制也是适用的。

拓展

在“Redis基本IO模型”图中,有哪些潜在的性能瓶颈?

Redis单线程处理IO请求性能瓶颈主要包括2个方面:

一、任意一个请求在server中一旦发生耗时,都会影响整个server的性能 也就是说后面的请求都要等前面这个耗时请求处理完成,本身才能被处理到。

耗时的操做包括:

  • 操做bigkey:写入一个bigkey在分配内存时须要消耗更多的时间,一样,删除bigkey释放内存一样会产生耗时
  • 使用复杂度太高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,可是N很大,例如lrange key 0 -1一次查询全量数据
  • 大量key集中过时:Redis的过时机制也是在主线程中执行的,大量key集中过时会致使处理一个请求时,耗时都在删除过时key,耗时变长
  • 淘汰策略:溜达策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都须要淘汰一些key,也会 形成耗时变长。
  • AOF刷盘开启always机制:每次写入都须要把这个操做刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能
  • 主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久

解决办法:

  • 须要业务人员去规避
  • Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操做放在了异步线程中执行,下降对主线程的影响

二、并发量很是大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,可是读写客户端数据依旧是同步IO,只能单线程依次读取客户端的数据,没法利用到CPU多核。

解决办法:

  • Redis在6.0推出了多线程,能够在高并发场景下利用CPU多核多线程读写客户端数据,进一步提高server性能
  • 固然,只针对客户端的读写是并行的,每一个命令的真正操做依旧是单线程的

参考资料

相关文章
相关标签/搜索