引言

Redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统。 Redis 采用网络 I/O 多路复用技术来保证在多个连接时,系统的高吞吐量(TPS)。

系统吞吐量(TPS)指的是系统在单位时间内可处理的事务的数量,是用于衡量系统性能的重要指标。影响系统吞吐量的因素很多,包括并发数和系统资源(CPU、内存、系统I/O操作、外部接口)等,系统资源等这些因素可以用平均响应时间指标来衡量

五种 I/O 模型

要理解 Redis 采用网络 I/O 多路复用技术,就得先了解五种 I/O 模型,如下:

  • 阻塞 I/O 模型

    最传统的一种 I/O 模型,即在读写数据过程中会发生阻塞现象。

    当用户线程发出 I/O 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态(block),用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态。

    举例如下:

    data = socket.read();

    如果数据没有就绪,用户线程就会一直阻塞在 read 方法。

  • 非阻塞 I/O 模型

    当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。

    所以事实上,在非阻塞 I/O 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 I/O 不会交出 CPU,而会一直占用 CPU,从而导致 CPU 占用率非常高。

  • 多路复用 I/O 模型

    多路复用 I/O 模型是目前使用得比较多的 I/O 模型。

    在多路复用 I/O 模型中,会有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 I/O 读写操作。因为在多路复用 I/O 模型中,只需要使用一个线程就可以管理多个 socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有 socket 读写事件进行时,才会使用 I/O 资源,所以它大大减少了资源占用(如 CPU)。

    会思考的你也许会想到,可以采用多线程+ 阻塞 I/O 达到类似的效果,但是由于在多线程 + 阻塞 I/O 中,每个 socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。

    而多路复用 I/O 模式,通过一个线程就可以管理多个 socket,只有当 socket 真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。

    另外,多路复用 I/O 为何比非阻塞 I/O 模型的效率高是因为在非阻塞 I/O 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用 I/O 中,轮询每个 socket 状态是内核在进行的,这个效率要比用户线程要高的多。

    值得注意的是,多路复用 I/O 模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用 I/O 模型来说,一旦事件响应太慢,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。

    这就是说,如果 Redis 每条命令执行如果占用大量时间,就会造成其他线程阻塞,对于 Redis 这种高性能服务是致命的,所以 Redis 是面向高速执行的数据库。

  • 信号驱动 I/O 模型

    在信号驱动 I/O 模型中,当用户线程发起一个 I/O 请求操作,会给对应的 socket 注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 I/O 读写操作来进行实际的 I/O 请求操作。

    这个一般用于 UDP 中,对 TCP 套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情。

  • 异步 I/O 模型

    异步 I/O 模型才是最理想的 I/O 模型,在异步 I/O 模型中,当用户线程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它收到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何阻塞(block)。

    然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它 read 操作完成了。

    也就是说,用户线程完全不需要关心实际的整个 I/O 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 I/O 操作已经完成,可以直接去使用数据了。

这五种 I/O 模型中,前面四种 I/O 模型实际上都属于同步 I/O,只有最后一种是真正的异步 I/O,因为无论是多路复用 I/O模型还是信号驱动 I/O 模型,I/O 操作的第 2 个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。

为何 Redis 要使用 I/O 多路复用技术

现在来回答这个问题就 so easy 啦!

首先,Redis 是单线程架构,所有的命令操作都是先进入队列,然后一个一个按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而采用 I/O 多路复用技术就是为了解决这个问题。

Redis 为何不采用异步 I/O 模型,这个不是效率更高吗?这玩意儿在多线程下才能发挥功效,而 Redis 是单线程架构哈。

epoll 是什么

epoll 其实只是众多实现 I/O多路复用模型的技术当中的一种而已,但是相比其他 I/O 多路复用技术(select, poll等),epoll有诸多优点(Redis 也支持 select 和 poll,默认使用 epoll)

  1. epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048,
  2. 效率提升, epoll 最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中, epoll 的效率就会远远高于 select 和 poll
  3. 内存拷贝, epoll 直接使用的 "共享内存",可以跳过传统的内存拷贝操作,效率更高

总结

Redis 采用 I/O 多路复用技术(epoll)是因为 Redis 是单线程架构,是为了避免网络 I/O 读写操作阻塞整个进程。

Redis I/O 多路复用技术原理的更多相关文章

  1. Lind.DDD.RedisClient~对StackExchange.Redis调用者的封装及多路复用技术

    回到目录 两雄争霸 使用StackExchange.Redis的原因是因为它开源,免费,而对于商业化的ServiceStack.Redis,它将一步步被前者取代,开源将是一种趋势,商业化也值得被我们尊 ...

  2. 大数据相关技术原理资料整理(hdfs, spark, hbase, kafka, zookeeper, redis, hive, flink, k8s, OpenTSDB, InfluxDB, yarn)

    hdfs: hdfs官方文档 深入理解HDFS的架构和原理 https://blog.csdn.net/kezhong_wxl/article/details/76573901 HDFS原理解析(总体 ...

  3. Redis单机数据库的实现原理

    本文主要介绍Redis的数据库结构,Redis两种持久化的原理:RDB持久化.AOF持久化,以及Redis事件分类及执行原理.最后,分别介绍了单机班Redid客户端和Redis服务器的使用和实现原理. ...

  4. 液晶常用接口“LVDS、TTL、RSDS、TMDS”技术原理介绍

    液晶常用接口“LVDS.TTL.RSDS.TMDS”技术原理介绍 1:Lvds Low-Voltage Differential Signaling 低压差分信号 1994年由美国国家半导体公司提出之 ...

  5. Oracle体系结构之控制文件的多路复用技术

    在Windows操作系统中,如果注册表文件被损坏了,就会影响操作系统的稳定性.严重的话,会导致操作系统无法正常启动.而控制文件对于Oracle数据库来说,其作用就好象是注册表一样的重要.如果控制文件出 ...

  6. 进阶的Redis之哈希分片原理与集群实战

    前面介绍了<进阶的Redis之数据持久化RDB与AOF>和<进阶的Redis之Sentinel原理及实战>,这次来了解下Redis的集群功能,以及其中哈希分片原理. 集群分片模 ...

  7. I/O多路复用技术(multiplexing)是什么?

    作者:知乎用户链接:https://www.zhihu.com/question/28594409/answer/52763082来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  8. 解读I/O多路复用技术

    前言 当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应.在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在键盘上键入命 ...

  9. Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》

    目录 前言 1. RDB 持久化 1.1 RDB 文件的创建与载入 1.2 自动间隔性保存 1.2.1 设置保存条件 1.2.2 dirty 计数器和 lastsave 属性 1.2.3 检查保存条件 ...

随机推荐

  1. Linux 磁盘管理(df fu fdisk mkfs mount)

    Linux 磁盘管理 Linux磁盘管理好坏直接关系到整个系统的性能问题. Linux磁盘管理常用三个命令为df.du和fdisk. df : 列出文件系统的整体磁盘使用量 du : 检查磁盘空间使用 ...

  2. 关gzip压缩,我有新发现

    1 gzip的压缩效果是立竿见影的: 2 网站是否开启gzip的查看方式 2.1 打开Chrome浏览器,按 F12打开调试面板 2.2 切换到network页签,在网络请求列表的表头,鼠标右键==& ...

  3. 嗨,你知道吗,Spring还有这些高级特性!

    目录 Spring介绍 设计理念 核心组件的协同工作 设计模式的应用 代理模式 策略模式 特性应用 事件驱动编程 异步执行 定时任务 日常开发使用非常多的Spring,它的设计理念是什么呢?有哪些核心 ...

  4. Java on Visual Studio Code的更新 – 2021年3月

    欢迎来到Java的VS Code更新.在过去的几个月中,我们的工程师一直在专注于一些非常重要的工作.现在,是时候揭开面纱了,开始吧. 类型层次结构(Type hierarchy) VS Code已经支 ...

  5. React函数式组件的性能优化

    优化思路 主要优化的方向有2个: 减少重新 render 的次数.因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 ...

  6. Vue和Node.js交互之token

    博主最近工作的时候,公司后台使用的JAVA,在做登陆时总会传来一个token然后我存在了本地存储中或Vuex中,之后每一次请求把它带在请求头上,然后就好奇想要自己做一个后台服务器然后做一个完整的登陆的 ...

  7. 通过中转DLL函数实现DLL劫持

    当我们运行程序时,一般情况下会默认加载Ntdll.dll和Kernel32.dll这两个链接库,在进程未被创建之前Ntdll.dll库就被默认加载了,三环下任何对其劫持都是无效的,除了该Dll外,其他 ...

  8. Wordpress主题编辑器漏洞复现

    Wordpress是全球流行的博客网站,全球有上百万人使用它来搭建博客.他使用PHP脚本和Mysql数据库来搭建网站. 那么,如果当我们在渗透测试过程中获得到了别人Wordpress的账号和密码之后, ...

  9. Win64 驱动内核编程-13.回调监控模块加载

    回调监控模块加载 模块加载包括用户层模块(.DLL)和内核模块(.SYS)的加载.传统方法要监控这两者加在必须 HOOK 好几个函数,比如 NtCreateSection 和 NtLoadDriver ...

  10. Portswigger web security academy:Server-side template injection(SSTI)

    Portswigger web security academy:Server-side template injection(SSTI) 目录 Portswigger web security ac ...