我们都知道,redis的高性能是具有多方面的因数,如:运行在内存上,单线程命令,io多路复用技术等,对于redis高性能的探究,就需要深入的研究其工作原理,这就涉及到redis的网络模型了,而需要探究redis的网络模型,就需要提前了解什么是用户空间,什么是内核空间,以及多路复用等技术。
一、用户空间与内核空间
一、前言
由于内核本身也是在操作和消耗各种资源,而用户程序也是在消耗这些资源(内存,cpu等),如果不加以限制,让用户程序随意操作这些资源,可能会导致用户应用冲突,从而产生内核崩溃。所以需要对应用程序与内核进行隔离。
二、用户空间和内核空间
一、对应用程序和内核进行隔离
1、进程的寻址空间划分为两部分:内核空间、用户空间
寻址空间:无论是内核还是用户程序,都无法直接访问计算机的物理内存空间,而是通过访问一个虚拟内存空间(虚拟内存空间与物理内存空间会有一个映射),这个虚拟内存空间就是一个无符号的整数虚拟地址,他的最大值就是(假如这台计算机是32位的)2的32次方,从0-2^32这个空间就是所为的寻址空间。一个内存地址代表的就是一个存储单元,也即一个字节,2^32就等于4G。
2、内核空间和用户空间命令权限的控制
用户空间:只能执行受限的命令(Ring3,等级最低的命令),而且不能直接调用系统资源,必须通过内核提供的接口进行访问。
内核空间:可以执行特权命令(Ring0,等级最高的命令),可以调用一切系统资源
三、栗子
以IO读写为例,Linux系统为了提高IO效率,会在用户空间和内核空间加入缓冲区。io读写会涉及到对磁盘(文件读取等)或者网卡的访问(网络访问)。
1、写数据时,要把用户缓冲数据拷贝到内核缓冲区中,然后写入设备
2、读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区
1、假设进程一开始进行简单的字符串运算,这些都属于基础命令,都是在用户空间进行,等到计算完了,需要写入到磁盘。这个时候就需要调用内核空间进行操作了
2、这个时候用户空间的数据会放入到缓冲区,内核空间从用户空间的缓冲区复制一份数据到内核缓冲区里,然后再把内核缓冲区数据写入到磁盘。
3、读取操作刚好相反。
注意: 这些操作用,读取数据等待数据从磁盘中返回和数据复制这两部分操作时最消耗性能的
二、阻塞IO
用户空间调用内核空间的recvfrom函数,内核空间去检查是否有数据,阻塞IO模型下,如果没有数据,会一直阻塞等待函数返回数据,这个时候函数也不会返回失败信息,而是会一直等待数据准备就绪(数据从硬件放入到内核空间缓冲区),这个时候如果数据准备就绪了,就会拷贝一份到用户空间,函数返回一个ok的结果给用户进程
阻塞IO:就是在这两阶段都必须阻塞等待
三、 非阻塞IO
用户应用调用内核空间函数进行数据的获取,如果数据还未准备就绪,函数直接返回失败,此时用户应用会反复去询问内核空间数据是否准备就绪,但是不会阻塞。一直到内核空间数据准备就绪,拷贝到用户空间,函数返回ok。
注意:在数据准备就绪到拷贝完成这段时间,线程是处于阻塞状态的
四、IO多路复用
一、文件描述符:
简称fd,是一个从0开始递增的无符号整数位,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件、视屏、硬件设备等,当然也包括网络套接字(socket)
二、IO多路复用:
是利用单个线程来同时监听多个fd,并在某个fd可读、可写时得到通知,从而避免无效的等待,充分利用cpu资源。
recvfrom函数:此函数是直接尝试去读取数据,读的目标就是某一个具体的fd,但是并不知道这个fd数据在内核空间是否准备就绪。如果没有就绪,就会产生阻塞或者一直轮询询问是否就绪等情况。(这个函数只能监听当前fd是否就绪,如果没有将会阻塞)
select函数:基于上面的情况,就需要确定fd是否准备就绪再调用recvfrom函数,这个时候就需要用到select函数了。此函数内部可以接受多个fd(此函数会把fd传入到内核空间),对多个fd进行监听(内核空间监听),如果有就绪了的,内核空间会返回给此函数。如果此时还是没有准备就绪的fd数据,那么此时进程只能进行等待,阻塞了。此时会有一个后台线程对fd进行监听,如果有准备就绪了的fd数据,将返回给select函数对应的结果。这个时候才会去调用recvfrom函数。这个时候如果有多个fd准备就绪,将会循环调用。
select、poll:只会通知用户进程有fd准备就绪,但不确定具体是哪一个fd,需要用户进程遍历fd来确认。
epoll:通知用户进程准备就绪的同时,把已就绪的fd写入用户空间。
1、 select模式
select是linux中最早的IO多路复用实现方案。
select模式存在的问题:
1、需要将整个fd_set从用户空间拷贝到内核空间,select结束后还得再拷贝回去。
2、select模式在用户空间无法得知具体是哪个fd准备就绪了,需要重新遍历fd_set数组
3、fd_set数组最大长度为1024,因此最大可以监听1024个fd
2、poll模式
3、epoll模式
1、事件通知机制
4、对比
5、完整流程
注意:
1、accept()函数处,如果有客户端请求连接,那么说明有ssfd可读,就会调用此函数,接收客户端socket,得到对应的fd,之后注册fd到红黑树中。如此重复。
2、当判断事件为读类型时,如果ssfd为可读类型时,那么说明有客户端连接进来了。如果为不可读时,说名是普通命令执行。
3、类型事件判断也可能会是异常事件。
五、信号驱动IO
六、异步IO
七、同步异步以及阻塞非阻塞
一、同步异步:同步、异步描述的是多个线程之间的协作关系,线程之间要么是同步,要么是异步。简单来说同步异步是对于调用者来说的。
同步:调用者需要收到被调用者返回的数据才执行后续操作。
异步:调用者不需要收到被调用者返回的数据,就进行执行后续操作。
二、阻塞和非阻塞:是同一个线程内部的执行状态,在一个时间点,线程要么阻塞,要么非阻塞。简单来说阻塞和非阻塞是对于被调用者来说的。
阻塞:被调用者由于各种原因线程执行权被抢占,当前线程挂起,等待执行,程序无法进行下去。
非阻塞:被调用者没有处理完当前任务,但是线程也不挂起,而是去处理其他事情。等到当前任务可以执行时再回来执行,但是此时是一直会去询问当前任务是否可以执行,