• 精华推荐 | 深入浅出学习透析Nginx服务器的基本原理和配置指南「Keepalive性能优化实战篇」


    在这里插入图片描述

    • Linux系统:Centos 7 x64
    • Nginx版本:1.11.5

    Nginx 是一款面向性能设计的 HTTP 服务器,能反向代理 HTTP,HTTPS 和邮件相关(SMTP,POP3,IMAP)的协议链接。并且提供了负载均衡以及 HTTP 缓存。它的设计充分使用异步事件模型,削减上下文调度的开销,提高服务器并发能力。采用了模块化设计,提供了丰富模块的第三方模块。所以关于 Nginx,有这些标签:「异步」「事件」「模块化」「高性能」「高并发」「反向代理」「负载均衡」「长连接」。本章内容主要就是针对于长连接请求模块。

    为什么要单独讲解keepalive指令?

    upstream设置中,有个参数要特别的小心,就是这个keepalive。

    大多数未仔细研读过nginx的同学通常都会误解这个参数,有些人理解为这里的keepalive是设置是否打开长连接,以为应该设置为on/off。有些人会被前面的keepalive_timeout误导,以为这里也是设置keepalive的timeout。

    但是实际上这个keepalive参数的含义非常的奇特,请小心看nginx文档中的说明,因为Keepalive长连接非常重要而且容易理解错误,所以专门做连一期专门讲解keepalive的文章。

    本文介绍如何使用Nginx进行配置和实现长连接Keepalive,并介绍如何设置 nginx 以提供静态内容服务,如何配置 nginx 作为代理服务器。

    keepalive指令

    在这里插入图片描述

    支持keepalive长连接,当使用nginx作为反向代理时,为了支持长连接,需要做到两点:

    • 从client到nginx的连接是长连接
    • 从nginx到server的连接是长连接

    从HTTP协议的角度看,Nginx在这个过程中,对于客户端它扮演着HTTP服务器端的角色。而对于真正的服务器端(在nginx的术语中称为upstream)Nginx又扮演着HTTP客户端的角色,keepalive指令出现在版本1.1.4。

    keepalive指令格式
    • keepalive不是on/off之类的开关
    • keepalive不是timeout,不是用来设置超时值
    Syntax:    keepalive connections;
    Default:    —
    Context:    upstream
    Activates the cache for connections to upstream servers.
    
    • 1
    • 2
    • 3
    • 4

    connections的取值代表着连接到upstream服务器的持续连接(即长连接)的数量。很多人都会有一个误解:认为这个参数是设置到upstream服务器的长连接的数量,分歧在于是最大连接数还是最小连接数

    官方文档的介绍

    The connections parameter sets the maximum number of idle keepalive connections to upstream servers

    • connections参数设置到upstream服务器的空闲keepalive连接的最大数量,这个”idle”的概念,何为idle。大多数人之所以误解为是到upstream服务器的最大长连接数,一般都是因为看到了文档中的这句话,而漏看了这个”idle”一词。

    When this number is exceeded, the least recently used connections are closed.

    • 当这个数量被突破时,最近使用最少的连接将被关闭。

    Nginx的官方文档给出了指示,否定了最大连接数的可能:keepalive指令不会限制一个nginx worker进程到upstream服务器连接的总数量请注意空闲keepalive连接的最大数量中空闲这个关键字

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

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

    • 再次假设请求和响应是均匀而平稳的,那么这1000条连接应该都是一放回连接池就立即被后续请求申请使用,线程池中的idle线程会非常的少,趋进于零。我们以10毫秒为一个单位,来看连接的情况(注意场景是1000个线程+100毫秒响应时间,每秒有10000个请求完成):

      • 每10毫秒有100个新请求,需要100个连接
      • 每10毫秒有100个请求结束,可以释放100个连接
      • 如果请求和应答都均匀,则10毫秒内释放的连接刚好够用,不需要新建连接,连接池空闲连接为零
    • 如果请求通常不是足够的均匀和平稳,为了简化问题,我们假设应答始终都是平稳的,只是请求不平稳,第一个10毫秒只有50,第二个10毫秒有150:

      • 下一个10毫秒,有100个连接结束请求回收连接到连接池,但是假设此时请求不均匀10毫秒内没有预计的100个请求进来,而是只有50个请求。注意此时连接池回收了100个连接又分配出去50个连接,因此连接池内有50个空闲连接。

      • 最后注意看keepalive=10的设置,这意味着连接池中最多容许保留有10个空闲连接。因此nginx不得不将这50个空闲连接中的40个关闭,只留下10个

    • 再下一个10个毫秒,有150个请求进来,有100个请求结束任务释放连接。150 - 100 = 50,空缺了50个连接,减掉前面连接池保留的10个空闲连接,nginx不得不新建40个新连接来满足要求

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

      • 再来一次类似的场景,假设请求是均匀的,而应答不再均匀,前10毫秒只有50个请求结束,后10毫秒有150个:

    • 前10毫秒,进来100个请求,结束50个请求,导致连接不够用,nginx为此新建50个连接

    • 后10毫秒,进来100个请求,结束150个请求,导致空闲连接过多,ngixn为此关闭了150-100-10=40个空闲连接

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

    现实世界中的请求往往和理想状态有巨大差异,请求不均匀,服务器处理请求的时间也不平稳,这理论上的大概1000个连接在反复的回收和再分配的过程中,必然出现两种非常矛盾场景在短时间内反复:

    1. 连接不够用,造成新建连接
    2. 连接空闲,造成关闭连接。从而使得总连接数出现反复震荡,不断的创建新连接和关闭连接,使得长连接的效果被大大削弱。
    Keepalive参数建议

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

    keepalive参数分析

    • 对应的参数设置为每个worker子进程在缓冲中保持的到upstream服务器的空闲keepalive连接的最大数量。当超过这个数量值的时候,会将最近最少使用(LRU)的连接进行关闭。需要考虑的是keepalive指令不会限制一个worker进程到upstream服务器连接的总数量。其参数应该设置为一个足够小的数字来让upstream服务器来处理新进来的连接。
    • 如果想让upstream每次都处理新的进来的连接,就应该将这个值放的足够小。反过来理解,就是如果不想让upstream服务器处理新连接,就应该放大一些?
    Keepalive的使用案例
    Keepalive对接memcached服务

    使用keepalive连接的memcached upstream配置的例子:

    upstream memcached_backend {
        server 127.0.0.1:11211;
        server 10.0.0.2:11211;
        keepalive 32;
    }
    server {
        ...
        location /memcached/ {
            set $memcached_key $uri;
            memcached_pass memcached_backend;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Keepalive对接Http1.1的Web服务

    对于HTTP,proxy_http_version指定应该设置为”1.1”,而”Connection” header应该被清理:

    upstream http_backend {
        server 127.0.0.1:8080;
        keepalive 16;
    }
    server {
        ...
        location /http/ {
            proxy_pass http://http_backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    Keepalive对接Http1.0的Web服务

    HTTP/1.0 持久连接可以通过传递”Connection: Keep-Alive” header 到upstream server, 但是不推荐使用这种方法。

    Keepalive对接FastCGI的Web服务

    对于FastCGI服务器,要求设置fastcgi_keep_conn来让长连接工作:

    upstream fastcgi_backend {
        server 127.0.0.1:9000;
        keepalive 8;
    }
    server {
        ...
        location /fastcgi/ {
            fastcgi_pass fastcgi_backend;
            fastcgi_keep_conn on;
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当使用默认的round-robin之外的负载均衡算法时,必须在keepalive指令之前激活他们。SCGI 和 uwsgi 协议没有keepalive连接的概念。

    保持和client的长连接

    为了在client和nginx之间保持上连接,有两个要求:

    • client发送的HTTP请求要求keep alive
    • nginx设置上支持keep alive
    HTTP配置

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

    http {
        keepalive_timeout  120s 120s;
        keepalive_requests 10000;
    }
    
    • 1
    • 2
    • 3
    • 4
    keepalive_timeout指令

    keepalive_timeout指令的语法:

    Syntax:    keepalive_timeout timeout [header_timeout];
    Default:    keepalive_timeout 75s;
    Context:    http, server, location
    
    • 1
    • 2
    • 3
    1. 第一个参数设置keep-alive客户端连接在服务器端保持开启的超时值。值为0会禁用keep-alive客户端连接。
    2. 可选的第二个参数在响应的header域中设置一个值“Keep-Alive: timeout=time”。这两个参数可以不一样。

    注:默认75s一般情况下也够用,对于一些请求比较大的内部服务器通讯的场景,适当加大为120s或者300s。第二个参数通常可以不用设置

    keepalive_requests指令

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

    keepalive_requests指令的实现原理
    • 指一个keepalive请求建立之后,Nginx就会为这个连接设置一个计数器,记录这个keep alive的长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则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较高的场景,非常有必要加大这个参数,以避免出现大量连接被生成再抛弃的情况,减少TIME_WAIT

    保持和server的长连接

    为了让Nginx和server(Nginx称为upstream)之间保持长连接,典型设置如下:

    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_set_header Host  $Host;
                proxy_set_header x-forwarded-for $remote_addr;
                proxy_set_header X-Real-IP $remote_addr;
                add_header Cache-Control no-store;
                add_header Pragma  no-cache;
                proxy_http_version 1.1;                    // 这两个最好也设置
                proxy_set_header Connection "";
                client_max_body_size  3072k;
                client_body_buffer_size 128k;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    • 如果不喜欢计算的同学也可以直接设置为keepalive=1000之类的,一般都OK的了。

  • 相关阅读:
    汇编语言全梳理(精简版)
    Android 12.0 SystemUI下拉状态栏定制化之隐藏下拉通知栏布局功能实现(二)
    带你深入了解Redis 高级特性
    小乌龟操作Git
    二叉树的搜索与回溯问题(leetcode)
    【Dotnet 工具箱】JIEJIE.NET - 强大的 .NET 代码混淆工具
    [C国演义] 第十七章
    企业有了BI,为什么还需要以指标为核心的ABI平台?
    马克·雷伯特访谈:机器人的未来及波士顿动力的创新之路
    .net OleDbParameter转SugarParameter
  • 原文地址:https://blog.csdn.net/l569590478/article/details/128064678