Nginx中保持长连接的配置 - 运维记录

 

在Nginx中,对于http1.0与http1.1是支持长连接的。http请求是基于TCP协议之上的,那么当客户端在发起请求前,需要先与服务端建立TCP连接,而每一次的TCP连接是需要三次握手来确定的,如果客户端与服务端之间网络差一点,这三次交互消费的时间会比较多,而且三次交互也会带来网络流量。当然,当连接断开后,也会有四次的交互,当然对用户体验来说就不重要了。而http请求是请求应答式的,如果我们能知道每个请求头与响应体的长度,那么我们是可以在一个连接上面执行多个请求的,这就是所谓的长连接,但前提条件是我们先得确定请求头与响应体的长度。对于请求来说,如果当前请求需要有body,如POST请求,那么nginx就需要客户端在请求头中指定content-length来表明body的大小,否则返回400错误。也就是说,请求体的长度是确定的,那么响应体的长度呢?先来看看http协议中关于响应body长度的确定:

1)对于http1.0协议来说,如果响应头中有content-length头,则以content-length的长度就可以知道body的长度了,客户端在接收body时,就可以依照这个长度来接收数据,接收完后,就表示这个请求完成了。而如果没有content-length头,则客户端会一直接收数据,直到服务端主动断开连接,才表示body接收完了。
2)对于http1.1协议来说,如果响应头中的Transfer-encoding为chunked传输,则表示body是流式输出,body会被分成多个块,每块的开始会标识出当前块的长度,此时,body不需要通过长度来指定。如果是非chunked传输,而且有content-length,则按照content-length来接收数据。否则,如果是非chunked,并且没有content-length,则客户端接收数据,直到服务端主动断开连接。

从上面,我们可以看到,除了http1.0不带content-length以及http1.1非chunked不带content-length外,body的长度是可知的。此时,当服务端在输出完body之后,会可以考虑使用长连接。能否使用长连接,也是有条件限制的。如果客户端的请求头中的connection为close,则表示客户端需要关掉长连接,如果为keep-alive,则客户端需要打开长连接,如果客户端的请求中没有connection这个头,那么根据协议,如果是http1.0,则默认为close,如果是http1.1,则默认为keep-alive。如果结果为keepalive,那么,nginx在输出完响应体后,会设置当前连接的keepalive属性,然后等待客户端下一次请求。当然,nginx不可能一直等待下去,如果客户端一直不发数据过来,岂不是一直占用这个连接?所以当nginx设置了keepalive等待下一次的请求时,同时也会设置一个最大等待时间,这个时间是通过选项keepalive_timeout来配置的,如果配置为0,则表示关掉keepalive,此时,http版本无论是1.1还是1.0,客户端的connection不管是close还是keepalive,都会强制为close。

如果服务端最后的决定是keepalive打开,那么在响应的http头里面,也会包含有connection头域,其值是”Keep-Alive”,否则就是”Close”如果connection值为close,那么在nginx响应完数据后,会主动关掉连接。所以,对于请求量比较大的nginx来说,关掉keepalive最后会产生比较多的time-wait状态的socket。一般来说,当客户端的一次访问,需要多次访问同一个server时,打开keepalive的优势非常大,比如图片服务器,通常一个网页会包含很多个图片,打开keepalive会大量减少time-wait数量。

先说说服务为什么使用HTTPs长连接技术?比如下面几个原因:
1)对响应时间要求较高;
2)服务走的是公网,客户端与服务端的TCP建立的三次握手和断开的四次挥手都需要40ms左右(真实数据包计算出来的),共需要80ms左右;
3)每个接入方使用的IP就若干个,需要建立的请求连接有限。
4)使用长连接技术,可以大幅减少TCP频繁握手的次数,极大提高响应时间;同时,即使使用长连接技术,也不需要消耗很多的系统资源用来缓存sockets会话信息。

          Nginx反向代理时保持长连接          

1)场景描述
HTTP1.1之后,HTTP协议支持持久连接,也就是长连接,优点在于在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。如果我们使用了nginx去作为反向代理或者负载均衡,从客户端过来的长连接请求就会被转换成短连接发送给服务器端。为了支持长连接,我们需要在nginx服务器上做一些配置。

2)要求
当使用 Nginx作为反向代理时,为了支持长连接,需要做到两点::
从Client到Nginx的连接是长连接
从Nginx到Server的连接是长连接

从 HTTP 协议的角度看,Nginx在这个过程中,对于客户端它扮演着 HTTP 服务器端的角色。而对于真正的服务器端(在 Nginx的术语中称为 upstream)Nginx又扮演着 HTTP 客户端的角色。也就是说对于Client客户端而言,Nginx其实扮演着Server的角色,反之,之于Server,Nginx就是一个Client。

3)保持和 Client 的长连接
要想做到Client与Nginx之间保持长连接,需要:
-  Client发送过来的HTTP请求要求携带"keep-alive"header。
-  Nginx设置支持keepalive

[Nginx中HTTP配置]
默认情况下,Nginx已经自动开启了对 Client 连接的 keepalive 支持。一般场景可以直接使用,但是对于一些比较特殊的场景,还是有必要调整个别参数。需要修改 Nginx的配置文件 (在 Nginx安装目录下的conf/nginx.conf):

http {
  keepalive_timeout 120s;        #客户端链接超时时间。为0的时候禁用长连接。即长连接的timeout
  keepalive_requests 10000;      #在一个长连接上可以服务的最大请求数目。当达到最大请求数目且所有已有请求结束后,连接被关闭。默认值为100。即每个连接的最大请求数
}

大多数情况下,keepalive_requests = 100也够用,但是对于 QPS 较高的场景,非常有必要加大这个参数,以避免出现大量连接被生成再抛弃的情况,减少TIME_WAIT。QPS=10000 时,客户端每秒发送 10000 个请求 (通常建立有多个长连接),每个连接只能最多跑 100 次请求,意味着平均每秒钟就会有 100 个长连接因此被 nginx 关闭。同样意味着为了保持 QPS,客户端不得不每秒中重新新建 100 个连接。因此,如果用netstat命令看客户端机器,就会发现有大量的TIME_WAIT的socket连接 (即使此时keep alive已经在 Client 和 NGINX 之间生效)。

keepalive_timeout 指令,语法如下:

Syntax: keepalive_timeout timeout [header_timeout];
Default:    keepalive_timeout 75s;
Context:    http, server, location

第一个参数设置 keepalive 客户端连接在服务器端保持开启的超时值。值为 0 会禁用 keepalive客户端连接。
可选的第二个参数在响应的 header 域中设置一个值 "Keep-Alive: timeout=time"。这两个参数可以不一样。
需要注意:默认 75s 一般情况下也够用,对于一些请求比较大的内部服务器通讯的场景,适当加大为 120s 或者 300s。第二个参数通常可以不用设置。

keepalive_requests 指令
keepalive_requests 指令用于设置一个keepalive连接上可以服务的请求的最大数量。当最大请求数量达到时,连接被关闭。默认是100。

这个参数的真实含义,是指一个keepalive建立之后,Nginx就会为这个连接设置一个计数器,记录这个keepalive的长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则 Nginx会强行关闭这个长连接,逼迫客户端不得不重新建立新的长连接。

这个参数往往被大多数人忽略,因为大多数情况下当 QPS(每秒请求数) 不是很高时,默认值 100 凑合够用。但是,对于一些 QPS 比较高(比如超过 10000QPS,甚至达到 30000,50000 甚至更高) 的场景,默认的 100 就显得太低。

简单计算一下,QPS=10000 时,客户端每秒发送 10000 个请求 (通常建立有多个长连接),每个连接只能最多跑 100 次请求,意味着平均每秒钟就会有 100 个长连接因此被 Nginx关闭。同样意味着为了保持 QPS,客户端不得不每秒中重新新建 100 个连接。因此,如果用netstat命令看客户端机器,就会发现有大量的TIME_WAIT的socket连接 (即使此时keep alive已经在 Client 和 Nginx之间生效)。因此对于 QPS 较高的场景,非常有必要加大keepalive_requests这个参数,以避免出现大量连接被生成再抛弃的情况,减少TIME_WAIT。

4)保持和Server的长连接
想让Nginx和Server之间维持长连接,最朴素最典型的设置如下:

http {
upstream backend {
  server 192.168.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
  server 192.168.0.2:8080 weight=1 max_fails=2 fail_timeout=30s;
  keepalive 300;       #这个很重要!
}   

server {
  listen 8080 default_server;
  server_name "";

location / {
  proxy_pass http://backend;
  proxy_http_version 1.1;                   #设置http版本为1.1
  proxy_set_header Connection "";           #设置Connection为长连接(默认为no)
  }
}
}

[upstream配置]
在upstream配置中,有一个参数特别重要特别小心,就是keepalive。大多数未仔细研读过 Nginx的同学通常都会误解这个参数,有些人理解为这里的 keepalive 是设置是否打开长连接,以为应该设置为 ON/OFF。有些人会被前面的 keepalive_timeout 误导,以为这里也是设置 keepalive 的 timeout。但是实际上这个keepalive参数的含义非常的奇特:

Syntax:    keepalive connections;
Default:   —
Context:   upstream

Activates the cache for connections to upstream servers.
激活到 upstream 服务器的连接缓存。

The connections parameter sets the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process. When this number is exceeded, the least recently used connections are closed.
connections 参数设置每个 worker 进程在缓冲中保持的到 upstream 服务器的空闲 keepalive 连接的最大数量. 当这个数量被突破时,最近使用最少的连接将被关闭。

It should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker process can open. The connections parameter should be set to a number small enough to let upstream servers process new incoming connections as well.
keepalive 指令不会限制一个 nginx worker 进程到 upstream 服务器连接的总数量。connections 参数应该设置为一个足够小的数字来让 upstream 服务器来处理新进来的连接。

在这里可以看到,前面的几种猜测可以确认是错误的了:
-  keepalive 不是 ON/OFF 之类的开关
-  keepalive 不是 TIMEOUT,不是用来设置超时值

很多人读到这里后,会产生另外一个误解:认为这个参数是设置到upstream服务器的长连接的数量,分歧在于是最大连接数还是最小连接数,不得不说这也是一个挺逗的分歧……

这里请特别注意Nginx文档里的这句话,至关重要:

The connections parameter sets the maximum number of idle keepalive connections to upstream servers
connections 参数设置到 upstream 服务器的空闲 keepalive 连接的最大数量

请仔细体会这个 idle 的概念,何为idle。大多数人之所以误解为是到upstream服务器的最大长连接数,一般都是因为看到了文档中的这句话,而漏看了这个idle一词。

然后继续看Nginx文档后面另外一句话:

When this number is exceeded, the least recently used connections are closed.
当这个数量被突破时,最近使用最少的连接将被关闭。

这句话更是大大强化了前面关于keepalive设置的是最大长连接数的误解:如果连接数超过keepalive的限制,就关闭连接。这不是赤裸裸的最大连接数么?但是 Nginx的文档立马给出了指示,否定了最大连接数的可能:

It should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker 
process can open.

特别提醒:keepalive 指令不会限制一个 nginx worker 进程到 upstream 服务器连接的总数量。

keepalive 参数的理解
要真正理解 keepalive 参数的含义,请回到Nginx文档中的这句:

The connections parameter sets the maximum number of idle keepalive connections to upstream servers
connections 参数设置到 upstream 服务器的空闲 keepalive 连接的最大数量

请注意空闲 keepalive 连接的最大数量中空闲这个关键字keepalive这个参数和之前http里面的 keepalive_timeout 不一样。这个参数的含义是,连接池里面最大的空闲连接数量。不理解?没关系,下面来举个例子:

先假设一个场景: 有一个 HTTP 服务,作为upstream服务器接收请求,响应时间为 100 毫秒。如果要达到 10000 QPS 的性能,就需要在 nginx 和upstream服务器之间建立大约 1000 条 HTTP 连接。nginx 为此建立连接池,然后请求过来时为每个请求分配一个连接,请求结束时回收连接放入连接池中,连接的状态也就更改为 idle。

再假设这个upstream服务器的keepalive参数设置比较小,比如常见的 10.

假设请求和响应是均匀而平稳的,那么这 1000 条连接应该都是一放回连接池就立即被后续请求申请使用,线程池中的 idle 线程会非常的少,趋进于零。我们以 10 毫秒为一个单位,来看连接的情况 (注意场景是 1000 个线程 + 100 毫秒响应时间,每秒有 10000 个请求完成):
1)每 10 毫秒有 100 个新请求,需要 100 个连接
2)每 10 毫秒有 100 个请求结束,可以释放 100 个连接
3)如果请求和应答都均匀,则 10 毫秒内释放的连接刚好够用,不需要新建连接,连接池空闲连接为零

然后再回到现实世界,请求通常不是足够的均匀和平稳,为了简化问题,我们假设应答始终都是平稳的,只是请求不平稳,第一个 10 毫秒只有 50, 第二个 10 毫秒有 150:
1)下一个 10 毫秒,有 100 个连接结束请求回收连接到连接池,但是假设此时请求不均匀 10 毫秒内没有预计的 100 个请求进来,而是只有 50 个请求。注意此时连接池回收了 100 个连接又分配出去 50 个连接,因此连接池内有 50 个空闲连接。
2)然后注意看keepalive=10的设置,这意味着连接池中最多容许保留有 10 个空闲连接。因此 nginx 不得不将这 50 个空闲连接中的 40 个关闭,只留下 10 个。
3)再下一个 10 个毫秒,有 150 个请求进来,有 100 个请求结束任务释放连接。150 - 100 = 50, 空缺了 50 个连接,减掉前面连接池保留的 10 个空闲连接,nginx 不得不新建 40 个新连接来满足要求。

可以看到,在短短的 20 毫秒内,仅仅因为请求不够均匀,就导致 nginx 在前 10 毫秒判断空闲连接过多关闭了 40 个连接,而后 10 毫秒又不得不新建 40 个连接来弥补连接的不足。

再来一次类似的场景,假设请求是均匀的,而应答不再均匀,前 10 毫秒只有 50 个请求结束,后 10 毫秒有 150 个:
1)前 10 毫秒,进来 100 个请求,结束 50 个请求,导致连接不够用,nginx 为此新建 50 个连接
2)后 10 毫秒,进来 100 个请求,结束 150 个请求,导致空闲连接过多,ngixn 为此关闭了150-100-10=40个空闲连接

特别提醒:第二个应答不均匀的场景实际上是对应第一个请求不均匀的场景:正是因为请求不均匀,所以导致 100 毫秒之后这些请求的应答必然不均匀。

现实世界中的请求往往和理想状态有巨大差异,请求不均匀,服务器处理请求的时间也不平稳,这理论上的大概 1000 个连接在反复的回收和再分配的过程中,必然出现两种非常矛盾场景在短时间内反复:
1)连接不够用,造成新建连接。
2)连接空闲,造成关闭连接。从而使得总连接数出现反复震荡,不断的创建新连接和关闭连接,使得长连接的效果被大大削弱。

造成连接数量反复震荡的一个推手,就是这个keepalive 这个最大空闲连接数。毕竟连接池中的 1000 个连接在频繁利用时,出现短时间内多余 10 个空闲连接的概率实在太高。上面情况说的都是keepalive设置不合理导致Nginx有多次释放与创建连接的过程,造成资源浪费。因此为了避免出现上面的连接震荡,必须考虑加大这个参数,比如上面的场景如果将keepalive设置为100或者200, 就可以非常有效的缓冲请求和应答不均匀。

总结:keepalive 这个参数一定要小心设置,尤其对于 QPS 比较高的场景,推荐先做一下估算,根据 QPS 和平均响应时间大体能计算出需要的长连接的数量。比如前面 10000 QPS 和 100 毫秒响应时间就可以推算出需要的长连接数量大概是 1000,然后将keepalive设置为这个长连接数量的 10% 到 30%。比较懒的同学,可以直接设置为 keepalive=1000 之类的,一般都 OK 的了。

[location配置]

http {
  server {
  location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;              #设置http版本为1.1
    proxy_set_header Connection "";      #设置Connection为长连接(默认为no)
  }
}
}

HTTP 协议中对长连接的支持是从 1.1 版本之后才有的,因此最好通过 proxy_http_version 指令设置为 1.1HTTP1.0不支持keepalive特性,当没有使用HTTP1.1的时候,后端服务会返回101错误,然后断开连接。而 "Connection" header 可以选择被清理,这样即便是 Client 和 Nginx 之间是短连接,Nginx 和 upstream 之间也是可以开启长连接的。这种情况下必须清理来自 Client 请求中的 “Connection” header。

[另外一种高级方式]

http {
  map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}   

upstream backend {
  server 192.168.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
  server 192.168.0.2:8080 weight=1 max_fails=2 fail_timeout=30s;
  keepalive 300;
}   

server {
  listen 8080 default_server;
  server_name "";

  location / {
  proxy_pass http://backend;
  proxy_connect_timeout 15;         #与upstream server的连接超时时间(没有单位,最大不可以超过75s)
  proxy_read_timeout 60s;           #nginx会等待多长时间来获得请求的响应
  proxy_send_timeout 12s;           #发送请求给upstream服务器的超时时间   
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection $connection_upgrade;
  }
}
}

http里面的map的作用是:
-  让转发到代理服务器的 "Connection" 头字段的值,取决于客户端请求头的 "Upgrade" 字段值。
-  如果 $http_upgrade没有匹配,那 "Connection" 头字段的值会是upgrade
-  如果 $http_upgrade为空字符串的话,那 "Connection" 头字段的值会是 close

[补充]
Nginx支持WebSocket。对于Nginx将升级请求从客户端发送到后台服务器,必须明确设置Upgrade和Connection标题。这也算是上面情况所非常用的场景。HTTP的Upgrade协议头机制用于将连接从HTTP连接升级到WebSocket连接,Upgrade机制使用了Upgrade协议头和Connection协议头。为了让Nginx可以将来自客户端的Upgrade请求发送到后端服务器Upgrade和Connection的头信息必须被显式的设置

[注意]
在Nginx的配置文件中,如果当前模块中没有proxy_set_header的设置,则会从上级别继承配置。继承顺序为:http, server, location。如果在下一层使用proxy_set_header修改了header的值,则所有的header值都可能会发生变化之前继承的所有配置将会被丢弃。所以,尽量在同一个地方进行proxy_set_header,否则可能会有别的问题。

           Nginx配置长连接 (ajax60秒请求超时)           
在使用ajax做轮训的时候前台发出的ajax请求总是会在60秒之后返回405超时响应,经过排除ajax超时响应设置后猜测nginx对请求进行了超时响应处理,猜测是nginx配置有问题;Nginx从 1.1.4 开始,实现了对后端机器的长连接支持,这意味着 Nginx 与后端机器的通信效率更高,后端机器的负担更低。

server {
   listen 80;
   server_name www.kevin.com;
location / {
    proxy_http_version 1.1; 
    proxy_read_timeout 600s;         #新增配置1
    proxy_send_timeout 120s;         #新增配置2
    proxy_pass http://127.0.0.1:8086;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

proxy_read_timeout: 连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
proxy_send_timeout: 后端服务器数据回传时间, 就是在规定时间之内后端服务器必须传完所有的数据。

posted @ 2018-07-25 10:14  散尽浮华  阅读(26363)  评论(1编辑  收藏  举报