2. nginx进程模型和数据结构

简介

  • 上一波总结了nginx的基本使用方法
  • nginx作为边缘节点所要承受的压力可能比业务服务器大几个数量级,意味着会把普通场景下的问题放大数倍
  • 这里进一步了解nginx处理流程,如何控制进程,解决并发难题

请求处理流程

  • nginx进程采用Master-Worker架构模型?
  • worker进程的数量和CPU核数匹配,为何这样设计?
  • 带着这两个问题,先来了解请求处理流程:
    nginx1
    • 核心在非阻塞的事件驱动处理引擎(epoll),需要状态机正确识别处理请求
    • 如果服务器内存不足,退化为阻塞调用,需要线程池(那堆白色波浪线)

进程结构

  • 单进程结构(不常用)
  • 多进程结构
    • 使用进程而不是线程,是因为线程共享同一进程的资源空间,如果某一线程出现类似地址越界的错误,此进程挂掉,其他线程也没了
    • 进程结构图:
      nginx2
    • master进程负责监控,worker负责处理请求
    • 缓存是进程间共享的,也交由worker处理,CacheM和CacheL负责管理缓存
    • worker和cache管理进程都是master的子进程
    • worker的数量一般要和CPU核数匹配,每个worker都能绑定一个核
  • 回顾进程命令
    • ps -ef |grep nginx 查看nginx的进程号
    • nginx -s reload 优雅重启nginx/重新加载配置文件
    • nginx -s stop nginx停止服务
    • nginx -s quit 优雅停止nginx,有连接时会等连接请求完成再杀死worker进程
    • 本质都是向Master进程发送信号

进程管理

  • 进程管理主要是进程通信,可以使用管道、共享内存或者信号,nginx使用信号,推荐博客
  • 注:这里相当于master对worker发信号,不是进程间数据通信
  • 这个人的相关文章也推荐,风格很喜欢,清晰明了值得学习
    nginx3
    • USER2WINCH是通过Linux的kill命令针对进程pid控制父进程的
    • 通常不对子进程进行上述命令,都是通过Master
    • nginx的命令行会在logs文件夹中找到pid执行
  • reload流程
    • 不停止服务(处理请求的同时),把配置文件平滑更新,进本流程如下:
      nginx4
    • 如果之前的worker进程出问题没有退出,一般会设置shutdown timeout,强制退出
      nginx5
  • 热部署完整流程
    • 旧的master进程一般不会立即关闭,方便回滚,具体流程如下:
      nginx6
    • 新的master进程还是旧master的子进程,但是用新的nginx二进制文件启动
  • 优雅的quit
    • 针对worker:
      nginx6

网络事件

  • nginx是事件驱动的,这里的事件主要是网络请求
  • 先了解一下网络请求的大致过程:(计算机网络需要掌握)
    nginx7
  • 如下图,从上到下会在各层协议的控制下添加头部传递,从下到上会识别各层头部信息将数据送到上一次处理
  • 在传输层获取TCP报文,这里规定了对应的端口号(应用的端口间通信,例如80)nginx8
  • 每收到一个报文,其实就是一个事件,可以分为读事件和写事件
  • 在任何异步事件的处理框架中(例如nginx),都会有一个事件收集分发器,会定义每一类事件的消费者(处理者):
    nginx9
  • 概念开始抽象起来了,此时就需要具象的理解一下,让自己先接受;
  • 之前说了TCP报文,里面包含的信息会交给对应端口的应用,这里交给nginx,在使用我们的应用配置之前(nginx.conf),要先对来的这波信息分析,干什么?谁干?然后才是怎么干!
  • 这是事件驱动下的异步处理框架的关键所在(大佬处理的是这部分)
  • 下面是通过wireshark抓包得到的TCP三次握手过程,感受一下读事件:
    nginx10
    • 这个软件可以安装一下,也可以看到nginx是如何响应建立链接的
  • nginx事件循环
    • 使用epoll,事件分发(创建进程)后会有事件队列(等着worker将其出队处理)
      nginx11
    • 问题来了,为什么要事件循环,岂不是有违事件驱动的思想?
    • 其实很简单,因为一个worker可能陆续接到多个事件请求,此时的关键不再是谁驱动,而是怎么调度CPU处理好所有的请求
    • 我们在nginx应用层面避免这种“轮询”是因为边缘可能压力巨大,节省性能
      nginx 11
  • epoll介绍
    • 既然提到epoll,回顾一下,核心主要有两点:就绪列表和功能分离
    • 功能分离指,相对于select,将维护等待队列(epoll_ctl)和阻塞进程(epoll_wait)分开处理
    • epoll是网络编程中的多路复用技术,是协调进程处理网络请求
    • socket是文件系统管理的对象,由进程创建,或者说socket对象中有进程的引用,方便唤醒此进程来处理接收到消息的socket
    • 流程如下:
      • epoll_create在内核创建eventpoll对象(就绪列表rdlist是其成员)
      • 可以用epoll_ctl添加或删除所要监听的socket(活跃链接)到eventpoll
      • 某个socket收到数据后,中断程序会操作eventpoll对象,并给rdlist添加此socket引用
      • 当程序执行到epoll_wait时,如果rdlist已经引用了socket(需要相关进程被唤醒处理socket),那么epoll_wait直接返回(不执行),如果rdlist为空,阻塞进程(释放CPU资源)
    • 将epoll当成一段处理进程的程序,eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态
  • 小结:nginx通过epoll运行自己的事件驱动框架(框架分发,epoll处理),epoll是管理进程多路复用的对象,主要思想是维护队列、进程阻塞、就序列表

请求切换

  • 使用上面的框架,带来的益处有哪些呢?首先体现在请求切换方面
  • 给nginx-worker分配较大的时间片(提高优先级),在用户态代码即可完成请求的切换(单核CPU分配,epoll协调)
  • 下图是普通的进程间切换(左)和用户态切换(右)流程:
    nginx12

相关概念

  • 阻塞调用
  • 非阻塞下的异步调用:
    nginx15
  • 一般情况下,会使用openresty或nginx的JavaScript模块实现非阻塞
  • 这里不是很懂,涉及到使用Lua语言,先记录!

nginx模块

  • 有很多开发者为nginx开发第三方模块,了解一个nginx模块从以下几方面入手:
    • 编译进nginx
    • 提供哪些配置项
    • 使用场景
    • 提供哪些变量
  • 进入官网可以看到所有模块的详细说明,这里就说说怎么看编译后的模块信息吧:
    • ngx_http_gzip_module为例
    • 进入编译后的objs目录,会看到ngx_modules.c,查看之,能找到一个引用数组ngx_module_t *ngx_modules[],就包含了所有被编译进来的模块
    • 再进入src/http/modules目录,可以查看ngx_http_gzip_filter_module.c文件,这是源文件,主要看以下几部分
      • 名为ngx_command_t的结构体,顾名思义,定义了指令名
      • ngx_http_gzip_filter_module_ctx定义此模块上下文
  • 每个模块都有所属的应用,由此将模块分为不同类型的子模块,每种模块会具体化ctx(context,上下文,即在此应用场景下遵循的标准配置)
    nginx16
    • 可以发现,所有模块都会注册在ngx_module_t(源码中都包含此结构体)
    • 这里的ctx_index顺序在一定程度上决定了优先级
    • type就划分了模块属于哪个应用
    • 所有模块都可以支持基本的nginx指令,例如reload、quit、stop等
  • nginx是如何划分模块的呢?
    nginx17
    • 从上图可以看出,nginx的模块结构很灵活,如果有新的应用分类,可以集成
    • 也可以称为四个基本应用,原生nginx框架只定义了核心模块CORE和配置模块CONF,故将其他的属于应用模块的称为子模块
      nginx18
    • 进入src目录可以看到四大应用模块
    • 里面可有可无的模块放在modules文件夹
    • 名称中,http/event/mail/stream即所属应用;带有filter即过滤响应请求模块,带有upstream即负载均衡模块,其他都是为生成响应的
  • 核心数据结构
    • 模块数据结构决定了在使用时占用的内存空间,在官网Core functionality的worker_connections中可以看到:
      Syntax:	worker_connections number;
      Default:	
      worker_connections 512;
      Context:	events
      
    • 连接和事件相对应
    • 在处理events时建立的连接(socket)需要初始化结构体数据ngx_connection_s,每个约占232字节,调用模块处理时ngx_event_s约占96字节,即连接数量越多占用越大
    • 一般高并发场景时,我们需要将连接数量设置的足够大
    • 模块中的变量会在access等log中频繁使用,可在官网Embedded Variables了解

内存池

  • 内存池可以预分配小块内存,可有效减少内存碎片,nginx中将小块内存碎片链接起来
  • 请求内存池:处理请求时使用,一般4K,响应结束释放
  • 连接内存池:建立连接时使用,一般256|512,链接关闭时释放
  • 在ngx_http_core_module中可以看到connection_pool_sizerequest_pool_size
  • 内存池在开发第三方模块时很常用,恰当配置很重要

进程通信

  • worker进程之间通信通过共享内存的方式,之前的进程管理使用信号的方式
  • 共享内存是worker之间最有效的通信手段,主要应用在流控等场景
  • nginx中使用共享内存的模块:
    nginx20
  • 共享内存就一定会出现竞争问题,需要加锁:
    nginx19

slab内存管理器

  • 将共享内存分为多个固定大小的slot(例如16K),类似于操作系统中虚拟内存的页式分配
  • slot大小定为多少合适呢?需要监控业务场景决定,淘宝开源了类似OpenResty的基于nginx的Web平台Tengine,可以将其中的ngx_slab_stat编译进来
  • 但并没有独立提供,下载其源码包,进入modules找到slab,将其编译到OpenResty(别搞错)
    ./configure --add-module=/tmp/tengine-2.3.2/modules/ngx_slab_stat
    gmake  # 不用管Error 2
    mv /usr/local/openresty/nginx/sbin/nginx /usr/local/openresty/nginx/sbin/nginx.old
    cp /tmp/openresty-1.19.3.1/build/nginx-1.19.3/objs/nginx /usr/local/openresty/nginx/sbin/
    
  • 测试代码:编写nginx.conf
    # gzip on;
    lua_shared_dict cat 10m;
    server {
    	......
    	location =/slab_stat {
                slab_stat;
        }
    
        location /set {
                content_by_lua_block {
                        local cat=ngx.shared.cat
                        cat:set("Roy",8)
                        ngx.say("STORED")
                }
        }
    
        location /get {
                content_by_lua_block {
                        local cat=ngx.shared.cat
                        ngx.say(cat:get("Roy"))
                }
        }
        ......
    }
    
    nginx20

nginx容器

  • 数组
  • 链表
  • 队列
  • 哈希树
  • 红黑树
  • 基数树

哈希表

  • 哈希表可以提高查询效率
  • 定义在结构体ngx_hash_t,指向连续的一组键,每个键指向用户结构体数据,每个这样的键值对称为ngx_hash_elt_t
    nginx21
  • 哈希表只为静态不变内容服务,bucket_size需要考虑CPU的cache_line对齐问题,一般是64字节
  • 以下是常见的模块对哈希表的配置:
    nginx22

红黑树

  • nginx在共享内存时常用红黑树管理对象,当然不止于此
  • 使用数据结构ngx_rbtree_t描述,红黑树是一棵自平衡二叉查找树(BST)
    nginx23
  • 红黑树里提供了很多方法,可以直接调用不用担心性能问题

动态模块

  • 仅仅编译动态库而不用替换整个二进制文件(sbin/nginx),在包含大量第三方模块热部署时非常有用,替换掉动态库即可,然后reload
  • 如何实现呢?
    • ./configure开始,只有部分模块支持动态
    • 搞个刚解压的nginx包,预备…
    ./configure --with-http_image_filter_module=dynamic	# 以图片动态库为例
    make
    make install
    
    • 安装目录中会多出个modules,里面就是动态库,以.so结尾
    • 配置文件nginx.conf,做个简单的图片缩放:
    load_module modules/ngx_http_image_filter_module.so;
    ......
    server {
    	......
    	location / {
    		root test;	# 图片放在test目录,按图片名访问
    		image_filter resize 15 10;
    	}
    	......
    	# 如果没效果禁用浏览器缓存
    }
    
  • 如何重新编译动态库呢?还是先configure dynamic,此时将objs中生成的.so文件copy到安装目录的modules中即可(反正是load嘛,很灵活!),无需make;

小结

  • 主要了解了nginx的请求处理流程、进程结构、进程管理。关键在于理解事件驱动下的events分发和多路复用进程管理方法epoll
  • 下一节将梳理繁多的nginx模块,学习模块指令的使用,理解变量的强大作用
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
银河麒麟是一个基于Linux系统的操作系统,而Nginx则是一个轻量级的Web服务器和反向代理服务器。要在银河麒麟上进行Nginx的离线安装,可以按照以下步骤进行操作: 1. 下载Nginx安装包:在银河麒麟的官方网站或者Nginx官方网站上下载适用于Linux系统的Nginx压缩包。确保下载的安装包版本与操作系统的架构相匹配。 2. 安装依赖软件包:在离线环境下,需要手动安装Nginx所依赖的软件包。可以通过在终端执行命令`sudo apt install`来安装所需的依赖软件包。具体的依赖包名称可以在Nginx官方文档或者银河麒麟的软件包管理器中查找。 3. 解压安装包:将下载好的Nginx安装包解压到指定目录,可以使用命令`tar -zxvf`来解压。解压后,会得到一个包含Nginx相关文件的文件夹。 4. 编译和安装:进入解压后的文件夹,执行`./configure`命令来进行编译配置。该命令会检查系统环境并生成对应的Makefile文件。然后执行`make`命令进行编译,最后执行`sudo make install`命令安装Nginx到系统中。 5. 启动Nginx:安装完成后,可以使用命令`sudo nginx`来启动Nginx服务器。可以通过`sudo systemctl start nginx`命令来启动Nginx的systemd服务。可以通过`sudo systemctl enable nginx`命令将Nginx设置为开机自启动。 通过以上步骤,就可以在银河麒麟的离线环境下成功安装Nginx服务器。在安装完成后,可以通过在浏览器中输入服务器IP地址来验证Nginx是否正常工作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Roy_Allen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值