Redis原理(1)-线程IO模型

108 阅读3分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

线程IO模型

Redis是个单线程程序

Redis单线程为什么还能这么快?

因为它的所有数据都在内存中,所有的运算都是内存级别的运算。正因为Redis是单线程,所以要小心使用Redis指令,对于那些时间复杂度为O(n)级别的指令,一定要谨慎使用,否则一不小心就可能会导致Redis卡顿。

  • Redis既然是单线程,如何能处理那么多的并发客户端连接?
  • 采用了多路复用,select 系列的事件轮询API,采用非阻塞IO

非阻塞IO

当我们调用套接字的读写方法,默认它们是阻塞的,比如read方法要传递进去一个参数n,表示最多读取n个字节后再返回,如果一个字节都没有,线程就会卡在那里,直到新的数据到来或者连接关闭,read方法才可以返回,线程才能继续处理。write方法一般来说不会阻塞,除非内核为套接字分配的写缓冲区已经满了,write方法就会阻塞,直到缓存区中有空间空闲出来。如下图套接字读写的细节流程。 在这里插入图片描述 非阻塞IO在套接字对象上提供了一个选项Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。能读多少取决于内核为套接字分配的读缓冲区内部的数据字节数,能写多少取决于内核为套接字分配的写缓冲区的空闲空间字节数。读方法和写方法都会通过返回值来告知程序实际读写了多少字节。

事件轮询(多路复用)

非阻塞IO有个问题,那就是线程要读数据,结果读了一部分就返回了,那么线程如何知道何时才应该继续读--也就是说,当数据到来时,线程如何得到通知。写也是一样,如果缓冲区满了,写不完,剩下的数据何时才应该继续写,线程也该得到通知。 事件轮询API就是用来解决这个问题的。最简单的事件轮询API是select函数,它是操作系统提供给用户程序的API。 在这里插入图片描述

指令队列

Redis会将每个客户端套接字都关联一个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务

响应队列

Redis同样也会为每个客户端套接字关联一个响应队列。Redis服务器通过响应队列来将指令的返回结果回复给客户端。如果队列为空,那么意味着连接暂时处于空闲状态,不需要去获取写事件,也就是可以将当前的客户端描述符从write_fds里面移出来。等到队列有数据了,再将描述符放进去,避免select系统调用立即返回写事件,结果发现没什么数据可以写,出现这种情况的现场会令CPU消耗飙升。

定时任务

Redis的定时任务会记录在一个被称为"最小堆"的数据结构中。在这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期里,Redis都会对最小堆里面已经到时间点的任务进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是select系统调用的timeout参数。因为Redis知道未来timeout的值时间内,没有其他定时任务要处理,所以可以安心睡眠timeout的值的时间。