redis6中的线程,单线程,多线程

redis6中终于引入了万众期待的多线程支持,之前在大家的传统概念里面,redis都是单线程来处理请求的,那么redis6中多线程又究竟是怎么回事呢?难道redis6中整个架构都调整了?不在是单线程来处理了?

要解答上面的问题,我们还需要重redis的历史设计中来找到答案。

总所周知,早前的TCP网络处理模式都是基于BIO模型,即所谓的阻塞IO来处理网络请求:一个客户端的请求建立,server端就对应用一个线程来处理这个客户端的读写,如果没有数据传输,则这个线程一直阻塞等待,知道有数据到来。

这种模型的缺点很明显,当并发量比较小的时候没有什么问题,但是如果并发量比较大的时候,每个请求都生成一个线程,非常耗资源。

随着计算机的发展,诞生了非阻塞IO,也可以理解为传说中的IO多路复用。一般底层系统调用基于select、epoll。当使用这种模型的时候,每个连接都会在内核注册一个文件句柄,内核会监测该句柄上的相关读写请求,当有请求到来的时候,内核会通知对应的程序来处理,这里的多路复用,可以理解为由原来的每个请求都要调用一次系统命令优化为 调用一次系统命令来获取所有有请求数据到来的连接,然后程序就可以对这些有数据的请求来进行处理。

redis的设计者也曾说过,redis的瓶颈并不在于cpu,基于内存的处理IO是可以忽略不计的,redis的瓶颈在于网络IO的读写请求。如果引入多线程来处理数据的话性能提升并不明显,反而由多线程带来的数据同步而影响性能。

因此redis在处理相关读写请求和数据处理的时候用的是单线程模型,这样能够避免线程上下文切换以及线程同步带来的问题,简化了编程模型,提高了系统的健壮性。

但是我们说的redis是单线程,并不是说redis中只有一个线程处理所有的事情,比如:

  1. redis中rdb持久化,就是通过系统底层调用,fork一个子进程,在子进程中完成了将内存中的数据写入到rdb文件中,父进程中的写入和读取通过写时复制,不会更改子进程中的内存数据,这时候父子进程各有一份自己的数据
  2. redis 4版本中引入了laze free,何谓lazy free,这个一般是用来删除大键或者flush db的时候,由于要删除的数据量太多,如果是在主线程-单线程中操作,会造成redis的堵塞,因此引入了lazy free的概念,主线程没有做实际的内存回收删除,只是做了unlink操作,将对应的key和内存解绑,由后台线程去做实际的删除、释放内存,没有laz free之前,为了不阻塞其他操作,删除大量数据只能每次删除100个这样的客户端循环发送删除命令。redis4中引入lazy free的同时也将原来redis整体设计中的聚合类型的存储结构进行了改进,在这之前redis内部用了很多的共享对象,比如客户端的输出缓存,redis并没有加锁来避免线程冲突(这时候就是单线程),redis去掉了共享对象,采用了数据拷贝。去掉了共享对象不仅实现了lazy free,也为后续的redis多线程带来了可能。

redis6中的所谓多线程的实现原来如下:

当客户端有请求时,主线程将有请求数据的读事件给到IO线程处理完成读操作,所有IO线程读取完数据之后,主线程开始处理这些请求(这里还是单线程),处理完之后将这些写事件分配给所有的IO线程进行写处理,等待所有的IO线程写处理完成。

可以看到redis6中所谓的多线程,并不是说处理数据采用多线程处理,redis还是单线程处理,只不过在网络IO这块,采用的多线程来处理读写,仅局限于网络IO的读写,而且redis的网络IO的多线程并不是我们理解的多线程,而是一次要么都是读,要么都是写,没法同时对网络IO进行读写。

上一篇:TCP和UDP套接字编程 (java实现)


下一篇:Redis6 的使用