Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,一次性放到文本事件分派器,事件分派器把事件分给事件处理器
在上图中:用户有三个socket连接,一个是建立连接的socket server,一个设置key的连接,一个是获取key的连接,这三个连接去请求Redis,会先请求到Redis内核里的IO多路复用器(epoll),然后由此放入到Redis事件队列中,然后由事件分派器分派给各个对应处理器。
1.这个IO多路复用把各个事件请求放入到同一个队列中也就解释了Redis为什么是单线程的.
2.IO多路复用(epoll)和事件分派器也就组成了一个由多事件到单处理的转换器。
IO多路复用是一种思想,天上飞的理念必然有落地的实现,所以Linux系统内核提供了一种底层的函数select-poll-epoll来实现Redis的单线程的模式,多IO的访问。
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的, 但是由于读写操作等待用户输入或输出都是阻塞的,所以I/O操作在一般 情况下往往不能直接返回,这会导致某一文件的1/O阻塞导致整个进程无法对其它客户提供服务,而I/O 多路复用就是为了解决这个问题而出现
所谓 I/O 多路复用机制,就是说通过一种机制,可以监视多个描达符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要select、poll、epoll来配合。多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
Redis服务采用Reactor的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为为文件事件处理器。
它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型
那么多的理念可能会让你觉得晦涩难懂,那么接下来我会尽量对里面的一些理念进行解析。
Redis基于Reactor模式开发了自己的网络事件处理器,这个处理器叫做文本事件处理器(file event handle)。
那么在此来归纳一下IO多路复用
IO:网路IO
多路:多个客户端连接(连接解释套接字描述符,即socket和channel)
复用:复用一个或多个线程,也就是说,一个或一组线程处理多个TCP连接,使用单进程就能实现同时多个客户端的连接
一句话:一个服务端进程可以同时处理多个套接字描述符,其发展可以分为select–poll–epoll三个阶段来描述。
我们再来看上面这张图片,事件派发器可以根据事件的类型来分配不同的处理器,但本身并不做请求,这个事件派发器像不像nginx。
其实Redis和nginx底层的牛逼之处就在于epoll操作系统内核函数。
调用者一直等待调用的结果,不出结果不会返回。
被调用方先返回应答,让调用者先回去,然后再计算调用结果,计算完成后就通知并返回给调用者。
同步和异步讨论的是被调用者(服务提供者),重要的是在于获取结果的消息通知方式。
调用者一直等待返回结果,当前线程会挂起,什么都不做
调用的请求被接受后不会阻塞该线程,而会立即返回。
阻塞非阻塞的讨论对在于调用者(服务请求者),重点在于等待消息的时候的行为,调用者是否能够做其他的事情。
1.同步阻塞
2.同步非阻塞
3.异步阻塞
4.异步非阻塞
同步阻塞:服务员–服务提供者说快到你了,先别离开我后台看一眼马上通知你。客户–服务请求者在海底捞火锅前台干等着,啥都不干。
同步非阻塞:服务员说快到你了,先别离开客户在海底捞火锅前台边刷抖音边等着叫号
异步阻塞:服务员说还要再等等你先去逛逛,一会儿通知你。客户怕过号在海底捞火锅前拿着排号小票啥都不干,一直等着店员通知
异步非阻塞:服务员说还要再等等,你先去逛逛,一会儿通知你。客户拿着排号小票刷着抖音,等着店员通知
在Redis中最重要的三个IO模型就是BIO,NIO,IO multiplexing。
本篇以这三个IO模型进行展开。
阻塞IO:
模型图
recvfrom():就是一个从(已连接)套接口上接收数据,并捕获数据发送源的地址的函数
当用户进程调用了recvfrom这个系统调用, kernel(linux内核)就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。所以,BIO的特点就是在IO执行的两个阶段都被block了。
那么既然阻塞是阻塞在哪里了?我们看下面的代码:
ps:因为Jedis的底层使用的就是Socket那么我就在此使用Socket来进行实验。
public class RedisServer
{
public static void main(String[] args) throws IOException
{
byte[] bytes = new byte[1024];
ServerSocket serverSocket = new ServerSocket(6379);
while(true)
{
System.out.println("模拟RedisServer启动-----111 等待连接");
Socket socket = serverSocket.accept();
System.out.println("-----222 成功连接");
System.out.println();
}
}
}
public class RedisClient01
{
public static void main(String[] args) throws IOException
{
System.out.println(