redis专题笔记 - io模型

同步、异步、阻塞、非阻塞

关于对同步异步阻塞和非阻塞的理解,在网上看到这样一个解释,感觉说的比较明白

同步阻塞:你打电话告诉老板你要买某书,老板拿起电话听你说完就去查书,没有说话,你什么也不知道,在得到任何结果之前,你一直拿着电话干等,你此时什么也干不了。30分钟后老板直接把书送到你家,这时你才挂断电话。每次电话你都要得得到结果(书到家)后你才挂断电话,这是同步。你一直拿着电话等结果,这是阻塞。

同步非阻塞:你打电话告诉老板你要买某书,老板拿起电话后说“我不知道有没有货,现在去查”便挂了电话,又过了10分种你第二次打电话说你要买某书,老板拿起电话说完“还没有查到,你再等会儿”便挂断电话。挂断电话5分钟后老板查到有书,但并没有主动打电话告诉你。你再次等待10分钟后第三次电话老板问结果,老板说“书有了,我给你送到家”,你断挂电话。每次电话你都要得得到结果(去查->还没有查到->有货)后你才挂断电话,这是同步。你每隔10分钟打电话询问结果,这是非阻塞。

异步阻塞:你打电话过去问老板有没有某书,老板说“我不知道有没有货,现在去查,先挂了电话,有结果告诉你,你等我电话”就挂掉电话。等电话期间你什么也不干,老板主动给你发短信通知你结果书有了,5分钟后希望老板现在把书送来,你再次打电话让老板送书,老板马上送书上门。老板主动给你发短信,这是异步。等待老板的短信期间你什么也没干,这是阻塞。

异步非阻塞:你打电话过去后问老板有没有某书,老板说“好的,有货我直接给你送上门”就挂掉电话。然后你想干嘛干嘛,等老板门到后你看书。等待老板主动给你送书上门,这是异步。挂了电话后你就想干嘛干嘛,这是非阻塞。

转自知乎,作者:张恒,链接:https://www.zhihu.com/question/19732473/answer/23434554

个人理解:

  1. 同步:方法/接口调用者在发起请求后,需要主动得知调用结果;要么一直等待方法/接口返回,要么定时查询结果
  2. 异步:方法/接口调用者在发起请求后,不用管结果如何;被调用方会在处理完成之后,通过回调的方式告知调用方结果
  3. 阻塞:方法/接口调用者在发起请求后,调用方不能做其他事情,只能阻塞(挂起)在原地,直到方法/接口有结果返回
  4. 非阻塞:方法/接口调用者在发起请求后,调用方可以立即去做其他事情

五种IO模型

知道同步异步、阻塞非阻塞的区别之后,再来理解各种IO模型就简单很多了

  1. 同步阻塞IO: 程序发起IO请求,一旦IO数据没有准备好,程序则一直阻塞,直到IO操作完成并返回
  2. 同步非阻塞IO: 程序发起IO请求,IO数据没有准备好则直接返回错误,用户程序再通过轮询的方式获取IO数据
  3. 信号驱动IO: 程序发起IO请求,会给对应的socket注册一个信号函数,然后程序继续执行其他的逻辑;如果socket数据就绪时,内核会发送一个信号给用户程序,用户程序接收信号后便会在信号函数中执行IO请求获取数据;这里仍然要用户程序发起IO读取数据,在读数据的过程中是阻塞的。
  4. 多路复用IO: 程序发起一次IO请求,监听多个IO对象(socket)并等待,当多个socket中有数据就绪时,就返回用户程序执行IO操作;一般多路复用IO会执行两次函数调用,select()发起监听并等待,recvfrom()执行数据拷贝
    • 个人理解对于用户程序来说,多路复用仍然是一个同步阻塞IO,因为它需要同步等待有数据就绪;对于内核来说,是一个同步非阻塞的过程,因为内核不会因为一个socket没有数据准备就阻塞在那个sokcet上,而是随即查询其他的socket
  5. 异步IO: 异步IO则不同于上面4中IO的处理方式;程序发起一次异步IO请求后,内核立即返回请求收到,程序不管结果直接执行其他事情;内核自己做完IO相关的所有事情,包括数据等待数据拷贝两个过程,内核完成后通知用户程序,程序可以直接处理数据(不同于信号驱动,后者还要自己发起IO取数据)

IO多路复用 - select/poll/epoll

  1. select执行IO多路复用流程:
    1. 用户程序创建多个文件描述符(fd),内核拷贝fd列表并监听每个socket的读、写、异常信息
      • select采用的是位掩码的模型,使用定长数组fds_bits[__FDSET_LONGS]存储fd,#define FD_SETSIZE 1024,所以默认select只能支持1024个socket的监听;参考链接
    2. 采用轮询的方式遍历所有fd,并标记每个fd是否就绪,返回用户程序标记列表fd_set
    3. 拷贝fd_set到用户态空间,并返回就绪fd总数
    4. 用户程序需要再次遍历fd_set,并对已就绪的fd执行读写操作
  2. poll
    1. 除了使用pollfd结构体数组存储fd和select不同之外,其他基本一样
      • poll没有连接数限制
  3. epoll执行IO多路复用流程:
    1. 通过epoll_create()在内核高速缓冲区建立红黑树(存放文件描述符)和就绪fd链表(存放已就绪的fd)
    2. 通过epoll_ctl()在红黑树上添加待监听的fd,同时会给每个fd注册回调函数,回调函数将已就绪的fd存放到就绪fd链表
      • 回调行为是每个就绪的fd主动执行的,不需要内核再遍历所有fd查看就绪状态,因此epoll不会因为fd数量很大导致性能下降,epoll仅需要遍历已就绪fd列表
      • selectpoll因为需要遍历所有fd,所以当fd数量很大时,性能线性下降
    3. 通过epoll_wait()等待已就绪描述符返回;epoll_wait()仅监听就绪fd链表,并返回就绪fd数量
    4. 用户程序获取已就绪fd的列表不需要再从内核空间拷贝,而是通过mmap让内核和用户态共用一块内存空间
      • 这一点的细节还不太清楚。。。

redis线程IO模型

redis的IO模型大致可以描述为下图(自己脑补,不具有权威性,不保证准确性):

在这里插入图片描述

  1. redis默认每个套接字都是非阻塞IO
  2. 使用IO多路复用(图中展示select,但鉴于select的劣势,在unix系统基本使用epoll),主线程循环监听多个socket
  3. 当每次循环中有事件就绪时,主线程则处理对应的就绪事件
    • 如有新指令到来,主线程在hand_read()方法中,解析指令并从内存中获取数据、
    • 如有数据已经获取完成,主线程在hand_write()方法中将数据写入socket缓冲区中
  4. 每次循环中会执行其他事情,如定时任务
    • 定时任务通过最小堆的数据结构存储
    • 每次循环会看最小堆中最上方的数据是否可以执行,如有则执行定时任务
    • 循环末尾,主线程会看下一个待执行的定时任务还有多久,并设置为select的timeout字段;避免redis没有请求而阻塞其他事件的执行

问题

1、 既然redis是单线程的,为什么客户端还要维护redis的线程池呢???

客户端连接并非和“redis的单线程”一一对应,而是每个客户端连接和redis服务端构建成一个独立的套接字,redis的执行线程通过io多路复用的方式监听多个套接字事件
那为什么又需要多个连接呢?是因为单个连接可能造成网络传输能力有限,多个连接可以更好的利用网络带宽;客户端需要使用连接池则是为了避免频繁的创建连接

2、 有人说io多路复用其实也是同步阻塞IO

我个人对这个说法持不反对态度
对于用户程序来说,发起select请求之后确实是保持同步且阻塞的状态,所以对用户程序来说确实是同步阻塞IO;
但是,在内核层面来说,内核对每个socket的监听又是非阻塞的,因为内核同时监听多个socket,不会因为单个socket没有数据就阻塞不去查看其他socket的状态
另一方面,我感觉在IO多路复用的概念出来之前,同步阻塞IO更多指的是一个线程对单个socket的IO请求,IO多路复用提出了一种新的IO处理模式,即一个线程同时监听多个socket,故而和同步阻塞IO存在一些区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值