草庐IT

redis的IO模型

生不悔改 2023-03-28 原文

一、什么是IO模型

我们的应用都是部署在linux系统中,linux系统也是一种应用,它是基于计算机硬件的一种操作系统软件。当我们接收一次网络传输,计算机硬件的网卡会从网络中将读到的字节流写到linux的buffer缓冲区内存中,然后用户空间会调用linux对外暴露的接口,将linux中的buffer内存中的数据再读取到用户空间。这一次读操作就是一次IO。同样写也是这样的。

不同的操作系统,IO模型不一样,下面介绍的是Linux系统的几种IO模型

这样做是为了保护Linux操作系统,避免外部应用或者人为直接操作内核系统。

当线程操作在用户空间时候的状态称为:用户态
当线程操作在内核空间时候的状态称为:内核态

IO的性能瓶颈:
a.用户态与内核态的切换(数据拷贝)
b.读写线程的阻塞等待

linux的IO模型就是针对这两点去优化的

image.png

二、Linux的IO模型

1.阻塞IO模型

当用户应用线程调用linux操作系统的recvfrom函数读取数据的时候,如果内核的buffer内存中没有数据,那么用户线程会阻塞等待,直到内核的buffer内存中有数据了,才去将内核的buffer内存中的数据拷贝到用户应用内存中。
类似于你排队买包子,但是包子这时候没有了,但是你不知道还有没有包子,如果没有,你只能在那等待包子出炉,什么也做不了,干等着,直到包子出炉了,你才能拿到包子,放到自己的口袋中。

image.png

问题:
当一个线程阻塞住了,会导致后续所有的线程都阻塞住,即使后面的读写数据已经就绪,也无法进行读写。

2.非阻塞IO模型

当用户应用线程调用linux操作系统的recvfrom函数读取数据的时候,如果内核的buffer内存中没有数据,那么用户线程会直接拿到结果(没有数据)不需要等待,于是又会发起一次recvfrom函数调用,直到内核的buffer内存中有数据了,才去将内核的buffer内存中的数据拷贝到用户应用内存中。
类似于你排队买包子,老板直接和你说没有包子了,你已经知道了结果,你一遍又一遍的问老板,还有没有包子了,直到老板出炉了包子,告诉你,你才能拿到包子,放到口袋中。

image.png

问题:
如果一直没有数据的话,线程会死循环的调用recvfrom函数,频繁使用CPU资源,导致CPU资源的浪费。

3.IO多路复用

文件描述符(FD)

内核kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。文件包含音频文件,常规文件,硬件设备等等,也包括网络套接字(Socket)。
IO多路复用就是利用单线程去监听多了文件描述符FD,并在某个文件描述符FD可读,可写的时候接收到通知,避免无效的等待,充分利用CPU资源。

select模式

用户应用线程调用select函数去监听多个FD文件描述符,如果没有数据,还是要等待,如果有就绪的文件FD,说明有数据,那就去读对应的FD就绪的文件数据,此时内核会将文件FD集合拷贝到用户内存中,然后去遍历FD集合,找到可以读的数据的FD,然后再去读取,读完了之后会将FD的集合再拷贝到内核内存中。
类似于你去餐厅排队点餐,这时候有一个服务员,服务员通过平板监控后厨,你只需要询问服务员有没有东西吃就可以了,如果没有你还是需要等待,但是如果有了,服务员通过监控就知道有东西可以吃了,就会让你点餐了。

image.png

select模式的问题:
a.需要将整个FD数组从用户空间拷贝到内核空间,select结束还要再次拷贝到用户空间
b.select无法得知是具体的哪一个FD就绪,需要便利整个FD集合(数组
c.select监听的FD集合(数组)大小固定是1024,底层设计写死是1024个。

poll模式

poll模式其实和select模式原理差不多,不同的点在于,poll模式底层加上了一个event事件,分成读事件,写事件,异常事件等等。
流程:
a.先添加需要监听的事件,是读事件,还是写事件,可以是多个事件
b.将监听到的事件FD,转换成链表,保存在内核缓冲区
c.内核缓冲区将事件FD链表拷贝到用户缓冲区,并返回就绪的FD数量
d.用户缓冲区判断就绪的FD数量,如果大于0则开始便利事件FD链表

poll模式的问题:
a.需要将整个FD链表从用户空间拷贝到内核空间,poll结束还要再次拷贝到用户空间
b.poll无法得知是具体的哪个就绪的FD事件,需要便利整个FD事件(链表

对比select模式
由于使用了链表,理论上事件个数可以是无数个,但是随着事件个数增多,链表的遍历性能会下降,而且当没有就绪事件的时候还是需要等待。

epoll模式

epoll模式是在poll模式的基础上再次改进,首先将存储事件的FD链表改成了红黑树(理论上也是无上限的),红黑树的遍历性能稳定,其次就是将具体的就绪事件单独复制出来然后拷贝给用户缓冲区,用户缓冲区拿到的是已经就绪的事件,无需遍历性能再次提升。
流程:
a.先将注册的监听事件
b.将所有的FD挂载在一个红黑树中
c.当FD就绪调用回调函数将对应的FD复制到一个链表中
d.将链表从内核缓冲区拷贝到用户缓冲区,并返回链表大小n
e.用户线程直接判断n大小,当n不为0的时候,直接读取链表(全部是就绪的FD)的数据即可

信号驱动IO

当用户应用线程调用linux操作系统的sigaction函数,直接返回,然后该线程去做其他事情了,当有数据来了的时候,内核空间会去递交信号给用户空间,此时用户空间会调用recvfrom函数去将数据从内核空间缓冲区拷贝到用户空间缓冲区,并处理数据。
类似于你点餐点完了,服务员会给你一个号码,然后你的餐好了,服务员会叫你的号码,然后你就去拿餐。

image.png

问题:
当调用的线程过多,对应的信号量会增多,SIGIO函数处理不及时,会导致保存信号的队列溢出;而且内核空间与用户空间频繁的进行信号量的交互,性能很差。

异步IO

性能上来说也是不错的,就是在实际开发中,需要控制它的线程并发数,所以实现起来会非常麻烦,所以使用很少

总结

三种IO多路复用对比来说epoll的效果是最好的。解决了select和poll模式中存在的问题。

而redis就是使用的epoll模式的IO模型。

有关redis的IO模型的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  7. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  10. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

随机推荐