值得学习借鉴的Nginx性能优化点
2014-02-10 19:04:28   来源:我爱运维网   评论:0 点击:

做后台和运维的同学都知道,nginx是一个性能非常优异的http服务器和反向代理服务器。其优异的性能来自作者苛刻的追求和不断的优化。下面汇...

做后台和运维的同学都知道,nginx是一个性能非常优异的http服务器和反向代理服务器。其优异的性能来自作者苛刻的追求和不断的优化。下面汇总了阅读代码过程中看到的优化点,这些优化对我们编写高性能服务器非常的有启发:

 

1. 关闭Nagle算法

Nagle算法在RFC1122中有提及,它最初的目的在于解决大量小包存在于网络从而造成网络拥塞的问题,是一种网络拥塞控制方法。它的主要职责是数据累积,它的实现有两个阀值,一个就是缓冲区到达了一定的数量,另一个就是等待了一定的时间。

http协议包普遍较大,Nagle算法在http协议中优化意义不大,甚至有可能导致网络数据包碎片化,提高网络负担。因此nginx选择关闭了Nagle算法。

 

2. 开启cork算法

cork是塞子的意思,它的实现就像用塞子把通道塞住,尽量等到数据包到达一个MTU的大小再发送出去。cork算法与Nagle算法有些类似,但是它的目的不同。cork算法的着重于提高网络的有效负载。这在连续的发送大量数据时尤其有效。

nginx在发送大文件或者静态文件时会临时开启cork算法,提高大数据发送效率。

 

3. 使用sendfile系统调用

为什么要使用sendfile

这里要举个栗子:假如你是一个http服务器,这时用户向你请求一张图片,你应该如何将这张图片返回给用户?

一般的过程如下:

a) 从磁盘打开并读取文件到内存中

b) 将内存中的文件通过网络套接口发送给用户

一张图片是一块很大的数据,在这个过程中,这块数据是这样流动的:

磁盘->操作系统缓存->用户态缓存->操作系统缓存->网络

看到了吗?数据经过了两次操作系统缓存,而且还需要到用户态的缓存中。是不是太绕了。

sendfile正是为了解决这个问题而生的。如果使用sendfile,这块很大的数据流就变成了这样:

磁盘->操作系统缓存->网络

这样是不是简单了很多,快了很多?

因此nginx在发送大文件时,使用了sendfile优化,同时还会开启tcpcork算法。

 

4. 开启TCP_DEFER_ACCEPT选项

TCP_DEFER_ACCEPT这个选项用于优化TCP协议从建立连接到接受数据的过程中服务器端的开销。在一般的侦听套接口中,当三次握手的完成以后,相应的链接就已经被放到accept队列中,这时服务器调用accept就会得到一个已连接套接字。但是这个套接字上也许并没有可读的数据,服务器反而要等待数据到来。

而使用了这个选项之后,三次握手完成之后,这个链接并不能被accept到,而是等到真正的数据达到时,才能够被accept得到已连接套接字。

nginx开启了TCP_DEFER_ACCEPT后,每accept到一个套接字,立刻就可以对这个套接字进行读操作,提高了效率。

 

5. 使用accept4

accept4是一个系统调用,它的功能和accept是一样的,只是accept4可以在接受新连接的同时对这个连接设置标志位。一般用于设置非阻塞标志。

nginx使用accept4减少了一次系统调用的次数。连一次系统调用都要省,可以看出作者在性能优化上做的努力。

 

6. 使用TCP_QUICKACK

这个选项影响的是当接受到数据时,ACK响应发送的时机。当此选项设置后并不是马上发送ACK响应,而是把即将发送的数据和ACK响应一起发送出去。此选项不是永久的,需要在每次recv后重新设置。

在响应速度非常快的情况下,开启这个选项可以有效的减少网络中ACK包的数量。nginx使用这个选项来优化网络流量。

 

7. 用户态锁

nginx采用的也是大部分http服务器的做法,就是masterworker模型,一个master进程管理多个worker进程,基本的事件处理都是放在woker中,master负责一些全局初始化,以及对worker的管理。

而多个worker必然涉及到共享和互斥的问题,必然会用到锁。nginx没有使用系统的信号量锁,而是使用了“共享内存+原子操作”实现的用户态锁。这样每次加锁解锁操作都不需要进入内核态,极大的提高了锁的性能。

具体的实现原理是:共享内存和gcc的提供的原子操作__sync_bool_compare_and_swap

 

8. 避免惊群

nginx的多个worker同时epoll同一个监听套接口时,容易产生惊群,即同时唤醒多个worker

nginx的解决方案是使用互斥锁,同时只能有一个workerepoll监听套接口,并且只有这个worker会进行accept。这样就避免的惊群。

另外,epoll监听套接口的worker会进入到post处理模式,即所有的请求先放到队列中,等待释放掉互斥锁,不再epoll监听套接口时,再处理请求。这样就极大的减少了epoll监听套接口的时间,提高了响应速度。

 

9. 降低操作系统默认的keepalive时间

操作系统默认的keepalive原理是发送一个长度为0tcp数据包,等待对端的ack响应。如果有响应则认为这个连接是alive的。

linux上执行:

cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

这意味着,在我们的服务器上,当一个连接连续两个小时都没有发送和接受任何数据时,它会发出第一个探针,然后每75秒发送一次,直到连续9次没有收到ack返回,这个链接将被标记为断开。

这个时间太久了,需要两个小时才能得到对端异常断开了连接。对进程来说就是拥有大量无用的套接字。

nginx降低了keepalive的时间,减少了无用套接字的数量。

 

10.自建内存池

nginx的内存管理最大的特点就是一个实体对应一个内存池,实体销毁时释放整个内存池。内存池只有分配接口,没有释放接口,只能整个销毁。

这种方式的优点是整块分配,整块释放,减少内存碎片。

举个栗子:比如用户的一次http请求,nginx为这次http请求分配一个内存池。这次http请求过程中需要的内存全部在内存池中分配,使用后暂时不回收内存。等待http请求全部处理完了,将整个内存池销毁。

 

11.时间管理

nginx中的时间管理非常的特别。http服务需要使用到各种格式的时间字符串,nginx的做法是master100毫秒将各式各样的时间字符串格式化到共享内存中供worker直接使用。而不是worker需要使用时自己格式化。

这种方式极大的减少了格式化时间字符串的操作,每100毫秒执行一次。

如果是worker各自格式化,每条日志,每次请求都需要进行格式化操作。

 

12.CPU优化

nginx在这CPU块做的优化有提高进程优先级,使用setpriority函数。

绑定CPU提高CPU缓存的命中率,使用sched_setaffinity函数。

相关热词搜索:Nginx 性能 优化

上一篇:浅解Facebook的服务器架构
下一篇:最后一页

分享到: 收藏