11_redis_线程 IO 模型
redis是单线程。
单线程还快的原因是:所有的数据都是在内存中,运算都是内存级别的。对于o(n)的指令要慎用,不然会造成卡顿
单线程处理并发:多路复用,非阻塞IO
非阻塞IO
当使用套接字读取的时候,默认是阻塞的,read方法需要传一个参数n,表示读取这么多的字节后返回,如果没有读取到这么多字节线程就会卡在那里,等新的数据过来,或者连接关闭,才会将数据返回。write方法一般是不会阻塞的,除非内核中为套接字分配的缓冲区已经满了,write方法就会阻塞,直到到缓冲区重新分配新的空间后。
非阻塞IO,是在套接字对象加入一个Non_Blocking。当这个选项打开的时候,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。读写多少取决于为内核为套接字分配的读写缓冲区内部的字节数。读写方法返回值会告知读写了多少字节。
事件轮询(多路复用)
飞阻塞IO有一个问题,就是结果读写到一半的时候就返回了,线程如何知道什么时候需要继续读咧?就是当数据来的时候,线程是如何知道呢?写的时候,怎么知道缓冲区满了,写不完了,剩下的数据该什么时候才能写呢?
事件轮询API就是解决这个问题的。
最简单的事件轮询API就是select函数,是操作系统提供的。输入是读写描述符列表read_fds&write_fds。输出就是对应的可读可写的事件,同时提供了timeout参数,当 没有任何事件到来,会等timeout时间,线程处于阻塞状态。一旦期间有任何事件过来,就会立即返回。拿到事件后,线程顺序处理相应的事件,处理完后过来轮询,于是线程进入了一个死循环,称之为事件循环,一个循环一个周期
因为通过select函数调用处理多个管道描述符 的读写事件,所以称之为多路复用API。现在操作系统多路复用已经不用select函数了,而是使用epoll (linux) ,kqueue(freebsd & macosx),因为select描述符多的时候性能变差。他们的本质都是差不多的。
事件轮询API就是java中的NIO技术。
指令队列
redis为每个客户端套接字都关联了一个指令队列,客户端的指令通过队列进行顺序处理,先到先处理。
响应队列
redis为每个客户端套接字也提供了一个响应队列,服务器通过响应队列将响应的结果返回给客户端。如果队列为空,意味着现在连接处于空闲状态,不需要获取事件,就是将客户端描述符从write_fds中移除。当队列有数据的时候,再将描述符添加到write_fds中。应避免selelct系统调用立即返回写事件,这样会使cpu瞬间升高。
定时任务
服务器在处理IO的时候还要处理其他的事情,如果线程阻塞在select上面,还是需要处理别的事情。
redis的处理方式是:
将定时任务记录到称之为最小堆的数据结构中,这个堆中,最快处理的任务排在堆的最上方, 每个循环周期会处理排在最上面的任务,处理完之后, 将下一个最快执行的任务还需要的时间记录下来,这个时间就是selelct系统调用的timeout的时间,因为知道在timeout时间内没有需要处理的任务,所以可以安心的睡眠。