Redis为什么这么快,带你了解线程IO模型!

1、Redis是单线程还是多线程?

Redis 是个单线程程序。

2、Redis为什么这么快?

数据都在内存中,内存操作当然比磁盘操作快。

3、Redis单线程如何同时处理多个请求?

(1) 使用非阻塞IO+IO多路复用技术处理。

平常我们自己写的socket程序都是read和write多少个字节返回,接收不够一直阻塞,成为阻塞IO,阻塞的时候线程干不了别的事情。套接字提供一个选项Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。有了非阻塞 IO 意味着线程在读写 IO 时可以不必再阻塞了,读写可以瞬间完成然后线 程可以继续干别的事了。

非阻塞 IO 有个问题,那就是线程要读数据,结果读了一部分就返回了,线程如何知道 何时才应该继续读。也就是当数据到来时,线程如何得到通知。写也是一样,如果缓冲区满 了,写不完,剩下的数据何时才应该继续写,线程也应该得到通知。

事件轮询 API 就是用来解决这个问题的,最简单的事件轮询 API 是 select 函数,它是 操作系统提供给用户程序的 API。输入是读写描述符列表 read_fds & write_fds,输出是与之 对应的可读可写事件。同时还提供了一个 timeout 参数,如果没有任何事件到来,那么就最多 等待 timeout 时间,线程处于阻塞状态。一旦期间有任何事件到来,就可以立即返回。时间过 了之后还是没有任何事件到来,也会立即返回。拿到事件后,线程就可以继续挨个处理相应 的事件。处理完了继续过来轮询。于是线程就进入了一个死循环,我们把这个死循环称为事 件循环,一个循环为一个周期。

每个客户端套接字 socket 都有对应的读写文件描述符。

read_events, write_events = select(read_fds, write_fds, timeout) for event in read_events:

handle_read(event.fd) for event in write_events:

handle_write(event.fd)
handle_others() # 处理其它事情,如定时任务等

因为我们通过 select 系统调用同时处理多个通道描述符的读写事件,因此我们将这类系 统调用称为多路复用 API。现代操作系统的多路复用 API 已经不再使用 select 系统调用,而 改用 epoll(linux)和 kqueue(freebsd & macosx)。

(2) 指令队列和相应队列

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

Redis 会将每个客户端套接字都关联一个响应队列,用来关联写事件。

如果队列为空,那么意味着连接暂时处于空闲状态,不需要 去获取写事件,也就是可以将当前的客户端描述符从 write_fds 里面移出来。等到队列有数据 了,再将描述符放进去。避免 select 系统调用立即返回写事件,结果发现没什么数据可以 写。出这种情况的线程会飙高 CPU。

(3) 定时任务

服务器处理要响应 IO 事件外,还要处理其它事情。比如定时任务就是非常重要的一件 事。如果线程阻塞在 select 系统调用上,定时任务将无法得到准时调度。那 Redis 是如何解 决这个问题的呢?

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

—————————————————

参考来源:http://ddrv.cn/a/258735

发布了173 篇原创文章 · 获赞 326 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/William0318/article/details/105316610