首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深底解析redis网络模型,到底什么是epoll ?

深底解析redis网络模型,到底什么是epoll ?

原创
作者头像
小草飞上天
发布2024-12-24 18:48:37
发布2024-12-24 18:48:37
6671
举报
文章被收录于专栏:java学习java学习

概要

Redis的网络模型在最初的时候是单线程,通过一系列的改进,逐步演进到目前最新版本已经版本引入了多线程IO模型,用来提供整体的性能。

在单线程模型中,Redis通过IO多路复用模型来处理大量网络请求,当在高并发场景下,网络IO成为性能的瓶颈。也就是我们常说的为什么redis是单线程的原因。

在多线程模型中,Redis也支持并发处理任务了,但核心的命令解析和执行任然是单线和的,但在I/O线程读取和解析客户端命令,则会使用多线程来执行。这样的话在io瓶颈处不需要在等待,而是并发执行多个任务,以提高性能。

问:为什么新版本已经引入多线程模型,在执行命令时仍然还是单线程?

第一:多线程模型的执行复杂底肯定是高于单线程的,因为需要考虑各种公共值的可见性,就需要引入锁来防止并发变更。复杂度指数型上升。

第二:多线程模型会cpu上下文同步和切换的开销。

那为什么还会引入多线程呢,原因在于,执行redis的命令执行是内存级别的,本身就很快,那么这块进行上线文切换的话,开销就划不来了。但是在网络io读取和解析客户端命令时,这时本身就慢,对于单线程模型来说,cpu本身就处于闲置状态,所以这时候如果可以多线程并发处理的话,性能会提升。

内核空间与用户空间

这空间是什么呢?为什么有内核空间跟用户空间呢?

通俗的说:内核空间就是只能操作系统及其相关模块才能操作的空间,用户空间则是一般普通程序可以操作的(也就是我们开发者可以操作的)

在 CPU 执行的所有指令中,有一些指令是非常危险的,如果错误的执行,则会导致系统崩溃。所以操作系统将这类可以大概率导致操作系统崩溃的指令都划分到内核空间。

Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区。

  • 写数据时,把用户缓冲数据拷贝到内核缓冲区。然后写入设备
  • 读数据时,从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

我们了解内核空间和用户空间的目的是因为:从select模型到epoll模型,整体的优化都是在于用户空间和内核空间之间的效率进行优化的。

阻塞模型

阻塞模型的核心原理是:应用程序向系统发起一个请求,请求会到内核空间,这个时候应用程序会开始阻塞并等待,直到内核空间把数据准备好,并将其从内核复制到用户空间,这个时候应用程序才会继续处理数据。

生活场景:你准备做饭吃,煮上饭后,你不知道饭什么时候好,所以就一直等着饭好,等饭好了在去做菜。

(正常情况下,应该是煮上饭,然后就去开始做菜)

非阻塞io

在非阻塞IO模型中,应用进程发送一个请求后,不会等待内核空间准备好数据,而是一直询问做好没,做好没。

生活场景:你想去商场吃饭,人特别多需要排队,服务员给你一个号,然后就逛街去了。但是你不知道号什么时间好,所以一会就过来问一次,到我没,一会过来问一次,到我没。

看下图我们可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高,而且忙等机制会导致CPU空转,CPU使用率暴增。

io多路复用

了解io多路利用之前,我们先了解一个概念就是FD

文件描述符(File Descriptor): 简称 FD ,是一个从0开始递增的无符号整数 ,用来关联Linux中的一个文件,在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,也包括网络套接字(Socket)。

IO多路利用:利用单个线程来同时监听多个FD , 并在某个FD可读,可写时得到通知,从而避免无效的等待,充分利用CPU资源。

也就是说你发送的一个请求到内核空间实际就是一个FD 。所谓IO多路利用就是监听FD,防止无效的等待。

有三种方式可以监听:select 、 poll 、epoll.(这3个是一个进化的过程,现在都用epoll)

select

select是Linux中最早的I/O多路利用的实现方案

它的原理是:

  1. 使用一个数组fd_mask存储fd。最大存储 1024个。其中数组值 0代表未就绪 1代表就绪
  2. 用户请求过来后,会将fd映射上 fd_mask 数组对应的索引。
  3. 如果要监听 fd = 1 ,则执行select 方法 ,将fd_mask 从用户空间 复制到 内核空间。
  4. 内核空间从索引0开始遍历fd_mask直到需要监听的fd的最大索引值,如果数据已经就绪则将值修改为1
  5. 将已经处理完成的fd_mask复制到用户空间。
  6. 用户空间响应。

存在的问题点:

需要将整个fd_set从用户空间拷贝到内核空间,select结束还需再次拷贝回用户空间

select无法得知具体就绪的fd,需要遍历整个 fd_set

fd_set监听的fd数不能超过1024

poll

poll模式对select模式做了简单改进,但性能提升不明显,

它的原理是:

①创建pollfd数组,向其中添加关注的fd信息,数组大小自定义

② 调用poll函数,将pollfd数组拷贝到内核空间,转链表存储,无上限

③ 内核遍历fd,判断是否就绪

④数据就绪或超时后,拷贝占ollfd数组到用户空间,返回就绪fd数量n⑤ 用户进程判断n是否大于0

⑥ 大于0则遍历pollfd数组,找到就绪的fd

相比于select.poll提升并不明显,主要在于:

解决了select模式中的fd set大小固定为1024的问题,poll中数组pollfd在内核中采用链表,理论上无上限。

虽然解决了大小问题,但理论上,如果fd越来越多,每次遍历都会消耗更多时间,性能反而会下降。

eooll

接下来就要说到我们的主角:epoll.

epoll 是基于事件驱动的 IO 方式,他将fd管理数组修改为了红黑树和链表,主要解决了每次内核空间和用户空间之前复制及遍历的问题。

它的原理是:

  1. 内核执行epoll_create创建,会在内核空间创建一个eventpoll。里面包含一个红黑树,记录所有需要监听的fd , 一个链表,记录已就绪的fd.
  2. 当一个请求过来时,向内核空间(红黑树)的发送一个添加fd监听的请求,这时候只有一个fd(解决了复制问题)。同时设置回调事件。
  3. 当链表不为空时,则会触发回调事件,通知用户空间已经准备就绪的fd.

总结

阻塞模型、非阻塞模型、IO多路复用,各有各的场景,但目前使用最多的就是IO多路利用。

epoll模式核心通过红黑树保存要监听的FD,理论上无上限保存fd,而且增删改查效率都非常高,性能不会随监听的FD数量增多而下降.每个FD只需要执行一次epoll ctl就可以添加到内核空间的红黑树,无需重复拷贝FD到内核空间。

附加:redis多线程的使用,我们分享一张图,大家看一下在哪块使用了多线程:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概要
  • 问:为什么新版本已经引入多线程模型,在执行命令时仍然还是单线程?
  • 内核空间与用户空间
  • 阻塞模型
  • 非阻塞io
  • io多路复用
    • select
    • poll
    • eooll
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档