鞭辟入里——线程IO模型
Redis是个单线程程序,因为数据都存放在内存中,所有运算都是内存级别的运算。正因为是单线程,所以要小心使用Redis指令,对于那些时间复杂度为O(n)级别的。使用IO多路复用来处理并发
非阻塞IO
当调用套接字读写的时候,默认是阻塞的。read方法需要读n个字节,若没有数据需要等数据到来或者连接关闭,write方法,如果写缓冲区已满,需要有空间空闲。
非阻塞IO在套接字上提供Non_Blocking,不会阻塞,而是能读多少读多少,能写多少写多少。读方法和写方法通过实际读写了多少字节来返回。
事件轮询(多路复用)
非阻塞IO有个问题,线程要读数据,结果只读了部分就返回,那么线程如何知道何时才应该继续读。
事件轮询Api,select函数同时提供一个timeout参数,线程处于阻塞状态。期间有任何事件到来,就可以立即返回。过了之后没有任何事件到来,也会继续返回。然后线程继续处理相应的时间,处理完了之后继续过来轮询
现代操作系统的多路复用API已经不再使用select,而改用epoll(linux)和kqueue(FreeBSD和macosx),因为select系统调用在描述符多的时候会变的非常差
指令队列
Redis会将每个客户端套接字都关联一个指令队列。排队顺序处理,先到先服务
响应队列
Redis同样为每个客户端套接字关联一个响应队列。Redis服务器通过响应队列来将指令的返回结果返回给客户端,如果队列为空,就将客户端的描述符从write_fds中移出,等有数据后在放入。避免系统调用立即返回写事件,但是没有数据
定时任务
服务器除了要响应IO事件外,还要处理其他事情。比如定时任务,如果线程阻塞在select系统调用上,定时任务将无法得到准确的调度
Redis的定时任务会记录在一个被称为"最小堆"的数据结构中,在这个堆中,最快要执行的任务排在堆的最上方,在每个循环周期里,Redis都会对最小堆里面已经到时间点的任务进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是select系统调用的timeout参数。因为Redis知道未来timeout的值的时间内,没有其他任务需要处理,可以安心睡眠timeout的时间