Nginx简介

Nginx简介

Nginx是高性能web服务器,使用量排世界第二。一些优点:单次请求更快的响应,高并发情况下更快响应,扩展性好,高可靠性,低内存消耗,单机支持10w以上并发连接,可提供热部署,开源等等。

架构设计

  • 模块化设计,高度模块化,有着抽象简单的接口,非常灵活。
  • 事件驱动架构,分为事件收集、分发者和各种事件的消费者
  • 请求的多阶段异步处理,把一个请求分为多个阶段,每次处理只能处理一部分,等下一次epoll循环再继续处理剩余的部分,取决于内核调度,配合事件驱动架构,使得每个进程都全力运转,尽量少出现休眠状况,具体是将阻塞方法分解为多个阶段,用定时器来代替循环检查标志位,若阻塞方法无法划分,用一个单独的进程来执行方法
  • 管理进程、多工作进程的设计,采用一个master管理进程,多个worker进程的工作方式,master进程监控worker进程,当worker进程出现问题,会启动新的进程避免性能下降,其次,master进程还支持运行时程序升级,配置项的修改等,worker进程间通过进程间通信来进行负载均衡,master进程和worker进程通过unix套接字进行通信
  • 可移植性
  • 使用内存池,减少内存碎片,减少内存申请次数,减少cpu消耗。

worker进程的工作方式

每个worker进程运行一个epoll事件循环,监听相同的端口,这会带来惊群问题,惊群指的是当新连接到来的时候,会唤醒所有监听端口的子进程,但是最后进行连接的只有最开始accept那个进程,其他进程accept失败继续休眠,这引起了不必要的上下文切换,增加了系统开销。解决方法是一个时刻只让一个进程监听端口,使用accept_mutex锁,监听时首先尝试持有锁,如果失败则不监听,若成功才监听端口,可能这个进程上有很多活跃的连接,可能占用锁很长时间,这样其他进程就不能监听了,nginx提供了2个post队列,一个用来放连接事件,另一个用来放普通读写事件,收到新连接时将连接事件放入连接post队列,接下来先处理连接post队列里面的请求,然后释放锁,再处理正常事件post队列的事件,这样就大大减少了锁的持有时间。

多个子进程之间还要实现负载均衡。和解决惊群的问题一样,需要accept_mutex锁,同时有一个变量ngx_accept_disabled,它是负载均衡的一个阈值。在启动时,这个阈值是一个负数,值为连接总数的7/8,负数时不会触发负载均衡,正数的时候才会触发负载均衡,当其为正数时,就不再接受新的连接,即是说当前连接使用达到总连接数的7/8时就不再接受新连接,每次循环将这个值减1,直到负数,才会尝试去持有accept_mutex锁处理新连接。伪代码如下:

1
2
3
4
5
6
7
if(ngx_accept_disabled)
ngx_accept_disabled--;
else{
if(ngx_trylock_accept_mutex() == NGX_ERROR)
return;
...
}

在一个事件循环中,主要调用ngx_processs_events_and_timers函数,处理网络事件和定时器事件,核心操作有3个:处理网络事件、处理两个post队列中的事件(上文提到)、处理定时器事件。

epoll的实现结构

简单提一下epoll的实现,epoll内部有一个红黑树用来保存所有监听的描述符及其对应的事件,和一个链表用来保存所有已经就绪的描述符,epoll_create时创建并初始化这个数据结构,epoll_ctl向红黑树中插入删除查找,效率很高,epoll_wait查看链表,若链表不为空就将链表中的内容从内核空间复制到用户空间。每个事件就绪后会用类似于回调方法的机制将自己添加到队列中,所有整个过程很高效。

TCP和Nginx

内核中的TCP和Nginx之间的关系。
连接时:
PaKW8J.png

发送数据:
PaKb5D.png

接受数据:
PaKzrt.png

配置项

Nginx的配置文件大致格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http{
server{
server_name A;
listen 80;
listen 127.0.0.1:8080;

location /L1{
root html;
...
}

location /L2{
...
}
}

server{
server_name B;
listen 80;
listen 127.0.0.1:8000;

location /L3{
...
}

location /L4{
...
}
}
}

可以看出一个配置文件可能有多个server块,server块下有多个location块,代表感兴趣的匹配url地址,每个server块代表了一个服务器,包括监听的端口号,感兴趣的url地址,根目录等。

upstream

Nginx通过upstream机制来提供反向代理服务。反向代理时,Nginx作为一个媒介,连接客户端与上游服务器,接受客户端的请求,将请求转发给上游服务器,再将上游服务器的结果转发给客户端。为什么要有这种设计呢?通常客户端与Nginx之前是外网连接,而Nginx和上游是内网连接,内网速度非常快,而外网较慢,利用Nginx的高性能可以尽可能的多处理客户端的请求从而减轻上游服务器的压力,上游服务器可以是Apache、Tomcat、memcached、mysql等。通常下游是HTTP协议,而下游是TCP协议。

当上下游网速差距不大,或者下游更快时,会开辟一块固定大小的内存,用来接受上游响应,并将其转发给下游,当下游接受速度慢,响应就会堆积在内存中,无法接受上游的响应。

当上游快于下游时,就要开辟足够的内存缓存来保存上游响应,内存满时还会将响应缓存到磁盘文件中。

分享到