当时做的笔记,现在找不到出处了,CSDN标记转载,必须要填写原地址,如果谁看到了 我立马改下 谢谢。
Redis的IO模型
也许你会怀疑高并发的Redis申间件怎么可能是单线程。很抱歉,它就是单线程,你的怀疑暴露了你基础知识的不足。莫要瞧不起单线程,除了Redis之外,Node.js也是单线程,Nginx也是单线程,但是它们都是服务器高性能的典范。
Redis单线程为什么还能这么快?
因为它的所有数据都在内存中,所有的运算都是内存级别的运算。正因为Redis是单线程,所以要小心使用Redis指令,对于那些时间复杂度为O(n)级别的指令,一定要谨慎使用,否则一不小心就可能会导致Redis卡顿。Redis既然是单线程,如何能处理那么多的并发客户端连接?这个问题,很多中高级程序员都无法回答,因为他们没听过“多路复用”这个词汇,不知道select系列的事件轮询API,没用过非阻塞IO。
Blocking I/O
传统阻塞io java默认的 io
I/O 多路复用
虽然还有很多其它的 I/O 模型,但是在这里都不会具体介绍。
阻塞式的 I/O 模型并不能满足这里的需求,我们需要一种效率更高的 I/O 模型来支撑 Redis 的多个客户(redis-cli),这里涉及的就是 I/O 多路复用模型了:
在 I/O 多路复用模型中,最重要的函数调用就是 select
,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select
方法就会返回可读以及可写的文件描述符个数。
关于
select
的具体使用方法,在网络上资料很多,这里就不过多展开介绍了;与此同时也有其它的 I/O 多路复用函数
epoll/kqueue/evport
,它们相比select
性能更优秀,同时也能支撑更多的服务。
Reactor 设计模式
Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
在了解I/O多路复用
之前,我们可以先了解一下传统的I/O
模型
同步阻塞I/O模型
首先,要从你常用的IO操作谈起,比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。无法处理并发
。
同步非阻塞IO模型
当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,这样是不会阻塞线程了。此时用户进程需要不断轮询,如果轮询频繁,则浪费了大量的CPU资源;如果轮询频率低,则不能实时地获取数据。过度浪费CPU资源
。
异步IO模型
当调用read时,不需要等到数据返回就可以继续去干别的事情,调用write的时候同理。但是频繁的切换线程会出现浪费过多CPU资源
的问题。
多路复用I/O模型
这里"多路"指的是多个网络连接,"复用"指的是复用同一个线程。
多路复用I/O模型是一种同步I/O模型。实现一个线程监听多个文件句柄(也叫做文件描述符,FileDescription,简称FD),当有一个FD就绪时,则通知对应的应用程序进行读写操作。当没有FD就绪时,就会阻塞并交出CPU。
多路复用I/O模型
是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作(比如多线程之间的切换,频换的切换线程会造成大量的资源浪费)。
采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)。
Redis为什么要引入多路复用I/O技术
I/O多路复用
的本质是同步阻塞I/O模型
,但是,它最大的优势在于可以在一次阻塞中监听多个文件描述符(FD)。我们带入redis的场景,来思考一下redis为什么使用多路复用I/O技术
。
- 首先采用普通的
同步阻塞I/O
,那么Redis可能会在一个客户端上长期阻塞。该客户端可能长期没有数据到达,而Redis需要处理多个客户端的通信,当其他客户端有请求到达时,Redis则无法处理了,这显然是无法接受的。 - 如果使用
同步非阻塞I/O
,那么就需要不断轮循客户端,那么这种频繁的轮循会很浪费CPU资源,如果轮循不频繁,那么可能就会出现数据不能实时获取的问题。 - 如果使用
异步IO模型
,线程的创建和频繁的上下文切换会浪费更多的资源。其次Redis本身就是单进程单线程的模式工作,多线程等待多个客户端显然与其系统思想不符。
综上,多路复用I/O技术是首选。
IO多路复用的三种实现方式
select | poll | epoll | |
---|---|---|---|
数据结构 | bitmap | 数组 | 红黑树 |
最大连接数 | 1024 | 无 | 无 |
FD拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝 |
工作效率 | 轮询:O(n) | 轮询:O(n) | 回调:O(1) |