关于`CPU` ``` --查看信息: cat /proc/cpuinfo | grep name | sort | uniq --查看核数: cat /proc/cpuinfo | grep "physical id" --直接查看:lscpu CPU(s): 2 2个逻辑CPU On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 1核 Socket(s): 1 1个CPU ``` 为什么`Nginx`采用`Master`和`worker`的架构模型? 为什么味`worker`进程的数量要和`CPU`核数匹配? `Nginx`请求处理流程 ``` --流量来自:web, email, tcp --处理引擎:非阻塞,事件驱动,需要状态机 传输层状态机 HTTP状态机 MAIL状态机 --线程池:处理磁盘阻塞调用 静态资源,磁盘缓存 Access日志 Error错误日志 --通过HTTP, Mail等协议与上游服务器交互 ``` 进程结构。单进程结构适合开发环境,多进程结构适合生产环境,默认是多进程结构。如果用多线程,由于线程之间共享同一个地址空间,一旦一个模块的某个线程出现问题,就会影响其它线程,这样达不到高可靠高可用。`Nginx`的`Master`进程用来管理`Worker`进程,监控每隔`Worker`进程是否在正常工作。而`Worker`进程用来处理真正的请求。另外`Cache Loader`进程用来处理缓存的载入,`Cache Manager`进程用来缓存管理。进程间的通讯通过共享内存来解决。`Nginx`的设计中,`Worker`进程可以多个,但原则是希望一个`Worker`进程从头到尾占据一个`CPU`。 进程结构演示。本质上当调用`nginx -s reload`类似的语句,本质上是向`Master`进程发送信号。 ``` --查看Nginx进程:ps -ef | grep nginx 当前进程id, 父进程id --重载:nginx -s reload 内部会重新启动worker进程和缓存进程 --再次查看进程: ps -ef | grep nginx worker process, cache manageer process父进程的子进程id发生改变 root 9170 1 master process nobody 16958 9170 worker process nobody 16959 9170 worker process nobody 16960 9170 cache manager process --删除master进程下的当前进程,重新生成其下的子进程: kill -SIGHUP 9170 --删除某个worker进程,重新生成一个worker进程:kill -SIGTERM 16958 ``` `Master`进程信号 ``` --监控worker进程的CHLD信号:当Worker进程挂掉,会向Master进程发送CHLD信号,Master进程就会重新启动该Worker进程 --接受信号 TEM, INT: 立刻停止Worker进程 QUIT:优雅退出Worker进程,不会向客户发出停止提醒 HUP:重新打开配置文件 USR1:重新打开日志文件 USR2:只能配合KILL使用 WINCH:只能配合KILL使用 ``` `Worker`进程信号。通常不对`Worker`进程直接操作,由`Master`进程来操作。 ``` TERM, INT: QUIT: USR1: WINCH: ``` `Nginx`命令行。当启动`Nginx`之后,会把`Master`进程的`pid`记录到一个文件中,默认记录到`/etc/nginx/logs`目录下的`nginx_pid`文件。执行命令行的效果和执行`KILL`的效果是等效的。 ``` reload:HUP reopen:USR1 stop:TEM quit:QUIT ``` 使用`nginx -s reload`的背后 ``` --向master进程发送HUP信号 --master进程校配置语法是否正确 --master进程打开新的监听端口 --master进程用新配置启动新的worker子进程 --master进程向老的worker子进程发送QUIT信号,优雅关闭保证平滑 --老worker进程关闭监听句柄,处理完当前连接后结束进程 ``` 热升级的流程。在进行热升级的时候,老的进程可能会出现一直推不掉,新的`Worker`进程同时存在问题,这时候需要考虑回滚。 ``` --备份旧的Nginx文件: --新的Nginx文件替换旧的Nginx文件:只替换binary文件,新的配置文件目录,日志文件目录和老的保持一致。在新版本的Linux中,如果一个文件正在使用,需要用-f进行强制替换。 --向master进程发送USR2信号:只能通过KILL USR2 pid, Nginx暂未支持命令行 --master进程修改pid文件名,加后缀.oldbin --master进程用新的Nginx文件启动新的master进程:这里会同时出现两个master进程,新的master进程起新的worker进程 --向老的master进程发送QUIT信号,关闭老master进程:老的master进程会优雅关闭老的worker进程 --如果回滚:因为老的master进程还存在,就向老的master进程发送HUP信号,向新的master发送QUIT信号 ``` 优雅地关闭`worker`进程。`worker`进程处理请求,识别当前的连接没有处理请求,再将连接关闭。优雅地关闭主要针对的是`HTTP`请求,而对于`TCP`、长连接等是做不到优雅关闭的。 ``` --设置定时器:worker_shutdown_timeout:在Nginx.conf中配置。当nginx -s reload内部会有一个有关优雅关闭的表示位 --关闭监听句柄:worker进程不会再处理新的连接 --关闭空闲连接:大多数时候,这些空闲连接在线程池中 --在循环中等待全部连接关闭:Nginx不是主动关闭,这部分时间可能比较长。判断标志位,如果一个连接没有请求,就关闭 --退出进程 ``` 网络收发与`Nginx`事件关系。事件主要指的是网络事件。`Nginx`的每个连接对应两个事件,一个读事件,一个写事件。客户端向`Nginx`发送请求的过程如下: ``` --客户端 -应用层:发送GET请求 -传输层:记录浏览器和Nginx的端口 -网络层:记录浏览器和Nginx的IP -链路层:以太网 --客户端路由器:运营商的IP --Nginx端路由器: --Nginx端 -链路层: -网络层:IP -传输层:端口 -应用层:Nginx开始处理 ``` `TCP/IP`协议层级。每次`Nginx`收到一个报文,就会分类成各种事件(报文对应`Nginx`的各种网络事件),这些事件可以看作是事件发布者,另外在`Nginx`中有事件的订阅者。也就是,所有的读和写都有对应的事件发布者和订阅者,`Nginx`内部机制中对这些事件进行集中或分发,也可以把这套机制看成是中介者模式。 ``` --数据链路层:HEADER里的源MAC地址,目的MAC地址。FOOTER里的Ethernet, ADSL, Wifi, PPP --网络层:Nginx的IP, 浏览器的IP --传输层:包含TCP, UDP协议,目的端口,源端口 --应用层: ``` 网络事件实例。 ``` --浏览器访问:ip:8080 --Wireshark --对Nginx所在IP进行抓包,对8080端口抓包 -TCP层:解决进程通讯问题。Source Port, Destination Port -HTTP层:解决机器和机器通讯问题。Source, Destination -浏览器发送SYN -服务器发送SYN,进行确认,此时Nginx还没有参与进来 -浏览器再次发送ACK,Nginx开始工作,Nginx把报文看作是读事件,建立连接的事件,Nginx调用accept方法 ``` `Nginx`如何处理事件?`Nginx`打开80或443端口,等待新的事件进来,新的客户端连上`Nginx`,客户端发起连接,对应`epoll`中的`wait`方法,`Nginx`处于`sleep`进程状态。当操作系统接受到一个建立`TCP`连接的报文,处理完三次握手以后,操作系统通知`epoll`的`wait`方法,唤醒`Nginx`的`worker`进程。操作系统内核会把事件放在事件队列中,从事件中获取到需要处理的事件(比如建立TCP连接事件,比如收到TCP请求报文),同时还会产生新的事件放到队列中。如果有些第三方模块消耗很长的CPU计算任务,会导致事件队列到了时间也得不到处理。 `Nginx`有事件分发机制,会从操作系统的`kernel`中获取到等待处理的事件。`Nginx`使用`epoll`网络事件搜集器模型。`epoll`在处理高并发连接中,每次处理活跃连接数占比很小。`epoll`每次取活跃连接的时候,遍历链表,这个链表里仅仅只有活跃的连接。当`Nginx`收到80端口建立连接的请求,添加一个读事件用来读取报文消息,与此同时,添加一个写事件,放到红黑树中,保证插入效率是`logn`.如果不想处理事件,只需要从红黑树中移除一个结点就可以。当读取一个事件,链表中的事件就没了。当操作系统接收到网卡中的报文,链表增加一个新的元素。 请求切换时事件处理框架如何处理?传统`web`服务器在处理高并发,需要进行进程切换,这样成本很高。`Nginx`要么一线程处理一连接,要么一线程同时处理多个连接,在用户态代码完成连接切换,减少OS进程切换。 阻塞和非阻塞,主要是指操作系统,或者底层的C库提供的方法或系统调用,如果方法导致进程进入`sleep`状态(当前条件不满足),操作系统主动切换为另外一个进程,这样是一个阻塞方法;非阻塞方法,永远不会当时间片未用完之时把进程主动切换掉。 同步和异步,主要是从调用方式而言,`Nginx`官方使用`javascript`同步的方式实现非阻塞效果,`OpenResty`基于`lua`语言同同步代码方式实现非阻塞高并发效果。 阻塞调用,以`accept`方法为例。调用的时候,如果监听端口所对应的队列,操作系统已经建立了三次握手成功之后的`socket`,阻塞方法可能会立刻得到返回。但是,如果队列是空的,操作系统会等待新的三次握手连接到达内核中。`Nginx`使用非阻塞调用。 `Nginx`的模块。需要具备的特点: - 可以被编译进`Nginx` - 可以提供配置项 - 可以何时被使用 - 可以提供变量 如何查看第三方模块的指令呢?如果通过二进制安装,进入`objs/ngx_modules.c`文件,可以查看所有被编译进`Nginx`中的模块。然后找到`ngx_command_t`,可以找出所有的模块指令。 模块分类。 ``` NGX_CORE_MODULE events NGX_EVENT_MODULE event_core epoll http NGX_HTTP_MODULE ngx_http_core_module 请求处理模块 响应过滤模块 upstream模块 mail NGX_MAIL_MODULE stream NGX_STREAM_MODULE config NGX_CONF_MODULE ``` 连接池增加对资源的利用率。当处理高并发,需要把`connection`的数目配置足够大。 内存池对性能的影响。对第三方模块开发有意义。 进程间会共享内存。进程间通讯的第一种方式是通过信号。第二种方式涉及数据共享,用共享内存。既然是共享,需要考虑锁。还有一种使用`slab`进行内存管理。 `Nginx`容器是实现高级功能的基础。 ``` --数组:多块连续内存。 --链表 --队列 --哈希表 --红黑树 --基数树 ``` 使用动态模块提升运维效率。