Nginx内存池申请内存原理

181 未经授权,禁止转载了解课程
课程介绍
讨论{{interaction.discussNum ? '(' + interaction.discussNum + ')' : ''}}
适合人群
1. 对Nginx技术有进阶学习/面试需求 2. 团队有意向或已经引入Nginx 3. Java开发工程师
你将会学到
本课Nginx的进阶课程,学员通过课程掌握Nginx的架构机制、模块设计和底层工作原理等。
课程简介

架构驿站,让更多人可以因为学习和分享而受益。

大师兄

2012年入行,毕业于北京交通大学计算机科学与技术专业,曾服役于广联达、三快在线(美团)、拉勾网,技术专家。


课程大纲:

image.png


课程内容节选:

事件驱动模型

通常,事件驱动框架主要由3部分组成:事件收集器(收集事件)、事件发生器/分发器/分发者(分发事件)、事件处理器/消费者。

通常,我们在编写服务器处理模型的程序时,基于事件驱动模型,“目标对象”中的“事件处理器”可以有以下几种实现办法:

  • “事件发送器”每传递过来一个请求,“目标对象”就创建一个新的进程,调用“事件处理器”来处理该请求。

  • “事件发送器”每传递过来一个请求,“目标对象”就创建一个新的线程,调用“事件处理器”来处理该请求。

  • “事件发送器”每传递过来一个请求,“目标对象”就将其放入一个待处理事件的列表,使用非阻塞I/O方式调用“事件处理器”来处理该请求(Nginx)。

上面的三种处理方式,各有特点:

  • 第一种,创建进程开销比较大,导致服务器性能比较差,编码实现较为简单。

  • 第二种,涉及到多线程开发、编码比较复杂,涉及到线程同步、有可能面临死锁等一系列的问题。

  • 第三种,编码逻辑比前两种都要复杂,大多数网络服务器都采用第三种方式,逐渐形成了所谓的“事件驱动处理库”。

不同操作系统提供了不同事件驱动模型,例如Linux 2.6系统同时支持epoll、poll、select模型,FreeBSD系统支持kqueue模型,Solaris 10系统支持eventport模型。为了保持其跨平台特性,Nginx的事件驱动框架完美地支持各类操作系统的事件驱动模型。

针对每一种模型,Nginx设计了一个Event模块,包括ngx_epoll_module、ngx_poll_module、ngx_select_module、ngx_kqueue_module等事件驱动模块。

事件驱动框架会在模块初始化时(Nginx启动时)选取其中一个作为Nginx进程的事件驱动模块。对于大多数生产环境中Liunx系统的Web服务器,Nginx默认选取最强大的事件驱动模型epoll。


(1)epoll原理详解

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,如下所示:

struct eventpoll {
  ...
  /*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,也就是这个epoll监控的事件*/
  struct rb_root rbr;
  /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件,需要处理的,有意义的*/
  struct list_head rdllist;
  ...
};

我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件。当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

所有添加到epoll中的事件都会与硬件设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法,它会把这样的事件放到上面的rdllist双向链表中。

一般来说,在程序的整个运行周期中只有一个epoll句柄。例如,nginx的每个工作进程只维护一个epoll句柄。句柄创建后,我们的程序监听的每个端口本质上都是文件描述符,可以在此上发生Accept事件,即接收客户端请求。因此,最初我们将添加与要通过epoll的ctl()方法监听的端口相对应的文件描述符。成功添加后,每个监听文件描述符对应于eventpoll红黑树中的节点。

我们在这里可以看到,在使用epoll的过程中,将有两种类型的文件描述符:

  • 一种是与我们正在收听的端口相对应的文件描述符,我们通常监听其Accept事件以等待客户端连接

  • 一种是与每个客户端连接对应的文件描述符,它监听其读写事件以接收数据并将其发送到客户端


在epoll中对于每一个事件都会建立一个epitem结构体,如下所示:

struct epitem {
  ...
  //红黑树节点
  struct rb_node rbn;
  //双向链表节点
  struct list_head rdllink;
  //事件句柄等信息
  struct epoll_filefd ffd;
  //指向其所属的eventepoll对象
  struct eventpoll *ep;
  //期待的事件类型
  struct epoll_event event;
  ...
}; // 这里包含每一个事件对应着的信息。

当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。

epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。

image.png

  • 执行epoll_create()时,创建了红黑树和就绪链表

  • 执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数

  • 执行epoll_wait()时立刻返回准备就绪链表里的数据即可

展开更多
发布
头像

{{ item.user.nick_name }} {{ EROLE_NAME[item.user.identity] }}

置顶笔记
讨论图
{{ item.create_time }}回复
  • 删除

    是否确认删除?

    确认
    取消
  • {{ item.is_top == 1 ? '取消置顶' : '置顶'}}

    已有置顶的讨论,是否替换已有的置顶?

    确认
    取消
{{ tag.text}}
头像
{{ subitem.user.nick_name }}{{ EROLE_NAME[subitem.user.identity] }}
{{ subitem.create_time }}回复
删除

是否确认删除?

确认
取消
发布
{{pageType === 'video' ? '讨论区抢占沙发,可获得双倍学分' :'讨论区空空如也,你来讲两句~'}}
发布
{{tips.text}}
{{ noteHeaderTitle }} 笔记{{ hasMyNote ? '我的笔记' : '记笔记' }}
{{ hasMyNote ? '我的笔记' : '记笔记' }}
优质笔记
更新于:{{ $dayjs.formate('YYYY-MM-DD HH:mm:ss', item.last_uptime*1000) }}
头像
{{ detail.username }}

公开笔记对他人可见,有机会被管理员评为“优质笔记”

{{ noteEditor.content.length }}/2000

公开笔记
保存
提问

讲师收到你的提问会尽快为你解答。若选择公开提问,可以获得更多学员的帮助。

记录时间点
记录提问时视频播放的时间点,便于后续查看
公开提问
提交