深度学习Nginx第四章:Nginx与网络事件以及驱动模型

  Nginx是一个事件驱动的,主要事件是指网络事件,Nginx每个网络连接自然对应两个网络事件,一个读事件一个写事件。

什么是网络事件:

应用层发送get请求,传输层主要做一个事情,打开一个端口,把自己端口和nginx端口记录下来,传输层把我们主机ip和目标主机公网ip记录下来,到达链路层通过以太网到我们加的路由器,路由器主要记录我们所在运营商的ip,通过广域网跳转到服务器b的路由器中,通过链路层和网络层以及传输层,通过传输层我们就知道给我们打开80或者443端口进程也就是nginx,nginx在他们http状态处理机里面处理这个请求。TCP层主要做进程与进程间通信的事情,IP层主要解决机器与机器间怎么找到的问题

报文:

TCP协议与非阻塞接口:

读事件:Accept:建立连接  、Read:读消息

写事件:Write写消息

其中在事件收集、分发器处理消费者,事件是一个生成者到nginx,不同时间段调用相对于消费者包括openrestry、lua同步代码

Nginx事件循环:

刚刚启动的时候在wait for events这里,打开80/443端口,等待新的事件进来,新的客户端连接事件,这样的事件对于我们epoll里面的epoll wait这个方法,当操作系统处理完握手流程完后,就会通知我们epoll wait阻塞方法,然后唤醒我们nginx  work进程,这里的kernel就是操作系统内核,然后把事件放在事件队列中,然后右图处理事件的循环,如果发现新的事件,比如发现一个链接新建立了。可能要添加一个操作时间,如果事件内浏览器没有发送请求则关掉链接,然后处理完后把响应写事件在操作系统写缓存,然后发给浏览器中,如果处理完后又到wait for events。 如果使用第三方模块导致cpu复杂运算,导致处理时间特别长,导致后续队列大量事件得不到处理,导致恶性循环,然后时间到了,nginx大量处理在连接和不正常断开,所以nginx不能容忍这个事情,很多第三方都不会一次使用cpu大量计算,而是分段使用

epoll的优劣:nginx的事件分发机制,epoll运行nginx事件驱动框架,在那个循环流程中,nginx怎么样能够从操作系统的kernel中获取等待处理的事件,nginx主要使用epoll这种网络收集器模型,epoll对于句柄数增加几乎是无关的,比较适合做大并发连接处理

   在处理事件的时候,虽然可能有一百万个并发连接,但是活跃的连接可能就这几百个,select、poll的实现是有问题的,每次取事件的时候都需要把这个一百万个连接扔给操作系统,让它一个个判断那些连接上面有事件进来,epoll就利用这个每次活跃连接占比非常的特点。每次取活跃连接的时候都会遍历一个链表,这个链表里面只有活跃的链接,这样效率很高,读取一个少一个(从内核态读取到用户态),当操作系统接收到网卡中的一个报文的时候,这个链表增加一个新的元素,获取句柄的时候主需要遍历这个链表。当我们Nginx收到80端口建立连接的请求后,添加一个读事件,用来读取http消息,这个时候可能再添加一个新的事件,写事件,放在红黑树中,这个二叉平衡树可以保证我们的时间复杂度是Logn,当我们不需要处理读事件或者写事件的时候我只需要移除这个事件,同样是Logn的事件复杂度

Nginx的请求切换:

 相比较传统的服务,比如apache,他们处理的时候是每一个进程处理一个请求,比如p1处理request1的时候,目前网络事件不满足的情况下就会切换到p2,可能很快不满足,比如写一个响应发现写缓冲区也已经满了,也就是网络中比较拥塞了,滑动窗口没有向前滑动以至于调用read方法无法写入这个字节,这个时候阻塞类写方法一定会导致我们进程间又发生了一次切换,切换到p3,这个时候p3有满足状态,接下来执行,这个时候能p3用完了他的时间片又被切换到p1,如此往复,每做一次切换在cpu的频率下大概是5微妙,但是并发连接和并发线程数增加的时候,这不是一个线性增加,是一个指数增加,会消耗我们很多的计算能力,传统的是在调用系统的进程调用方法来实现并发连接,而操作系统这种系统调用适用于数百上千的这种进程切换,再多上几万几十万就无法接受了

  nginx在请求的时候不满足的时候,用户态直接切换到绿色请求,这样就没有进程间切换的成本,除非是nginx的worker时间片已经使用到了,时间片长度一般是5毫秒-800毫秒,所以可以在配置上把worker的优先级加到最高,比如-19这样的话操作系统给我们分配的时间片比较大的,这样才能保证我们nginx在用户态比较好的完成切换

同步&异步、阻塞&非阻塞之间的区别

  阻塞和非阻塞主要是操作系统的底层c库提供的系统方法调用,可能这个方法导致进程进入了sleep状态,为什么呢?当前的进程不满足的情况下,操作系统把该进程切换到另外一个进程使用cpu了,而非阻塞方法是永远不会因为我们时间片用完的时候帮我进行切换,而同步和异步是我们编码方式而言

阻塞调用:

 当我们调用accept方法的时候,监听的端口所对应的accept队列里面有操作系统已经进行三次握手成功的socket在accept里面的话,当我们的accept队列为空的时候,等待系统调用新的三次握手的连接到内核中,我们才去唤醒accept调用,这个时间是可控的,所以流程中可以导致我们进程主动切换

非阻塞调用:

如果我们accept队列为空的时候,我们直接返回一个eagain错误码,不等待,这个时候代码收到收到这个特殊的错误码进行处理,所以就有一个问题,当我们的代码收到错误码的时候应该sleep一下,还是切换到其他任务的链接,所以我们使用异步的调用使用非阻塞调用时自然而然的,

Nginx模块

当打开gzip扩展模块的时候:

如果是第三方模块都有.c的这个原文件,都会有ngx_command_t这个结构体,结构体是唯一,这个结构体是一个数组,数组里都是他的指令名

配置是在ngx_command_t里,启停回调方法是体现在最下面的七个方法,包括master、worker进程将要启动和停止的时候都会提供回调方法,所以第三方模块都可以在我们master和woker进程进行优雅的退出和热部署热升级

 

Nginx通过连接池处理网络请求:

查看网络读写事件如何应用到NGINX上的,使用连接池增加资源的利用率,其中每个worker进程里都有一个独立的ngx_cycle_t这个数据结构,

其中connection使我们的连接池,通过nginx官网打开 Core functionality也就是核心功能,打开worker的worker_connections

  默认有多少个connection连接:

    默认是有512大小的数组,每个数组指向一个连接,但是512是非常小的,nginx一般都是十万以及百万级连接,这个连接不止客户端也用于上游服务器的,也就是我们做反向代理的时候,每个客户端消耗两个connection,每个connection连接通过序号对应一个读事件和写事件,所以其中有read_events和write_event,对应的大小是和连接的connection相同的

每个connection连接默认使用多大的内存呢?

在64位操作系统中占用232位字节,具体的版本不同可能占用有细微的差别,每个ngx_connection对应两个事件,读时间和写事件,每个事件的结构体占用96字节,所以每个连接的话是232+96*2个字节

ngx_event_s结构体中:

headler:是一个回调方法,很多第三方模块设置为自己实现。

timer:主要是作为超时使用的,基于rbtree实现的超时计时器,这个timer就是它的node,用来指向我们的读事件/写事件是否超时,这些定时器都是可配的

默认60s相当于在读事件上添加了60s的定时器。

当多个事件的时候形成一个nginx queue队列。

nginx_connection连接的结构体内:

off_t  sent:无符号的整形,表达的是这个连接上发送了多少字节变量

在nginx.conf中代表了发送了多少字节

Nginx进程间的通讯方式

   nginx是一个多进程形式,多个worker进程间通信就是使用共享内存

   如果是做数据的同步就是使用共享内存,就是打开一个10m的内存,多个worker进程可以同时进行写入和读取,为了更好使用共享内存就引入了锁,防止多个worker竞争,在早期还有基于信号量的锁(信号量是linux中比较久远的进程同步方式),导致进程进入休眠状态,发生进程切换,而现在使用的是自选锁,当锁的时候worker进程不断的请求锁,而信号量是休眠,不是不断请求锁。所     

  以要求worker进程快速的释放锁,不然会导致死锁的事情发生。

rbtree:比如我们做限速、流控,我们不容忍在一个内存中做的,必然其他的用户worker进程是无法感知的,所以要在共享内存。比如http cache 反向代理,所以这些模块有一个特点,插入、删除、 遍历特别的快,

单链表:把我们需要共享的元素串起来就可以了,

Ngx_http_lua_api:openresty的核心模块,定义了一个sdk

定义了一个10m的dogs,这里同时使用了红黑树和单链表,这里的dogs 10m 是使用红黑树来保存key-value,其中key 就是Jim,value是8,为什么需要链表这里是因为10m是有限的,因为这里代码涉及到应用代码这里很容易超过10m限制,这里有很多种处理方法,比如写入失败,但是lua 使用了lru淘汰,我们在做集群的流控上,跨worker进程间的通信必须要使用共享内存,而不能在worker内存中操作

共享内存的工具:Slab管理器

 主要是把共享内存切分给红黑树、链表的每个节点使用,首先会把共享内存分为很多页面,每个页面又切分为很多slot,比如32自字是一个slot,64、128字节等等,这里有一个51字节内存,将会放在64字节,所以会有内存的浪费  ,最多会有两倍内存消耗,特点是:适用于小对象、避免碎片、避免重复初始化(有的需要初始化,所以保持有原先的数据结构,避免初始化)

 如何做数据的监控和统计:

  ngx_slab_stau模块统计slab使用状态

nginx容器:

   可以达到变更配置达到最大化性能。

   最主要的六个容器:数组、链表、队列、哈希表、红黑树、基数树

  数组:多块连续内存,其中每块连续内存中可以存放元素

  哈希表:

  

与普通哈希表不同的:存储静态的资源,通常不会出现插入和删除

发布了83 篇原创文章 · 获赞 87 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/ligupeng7929/article/details/99712958