1 为什么要有Connection: keep-alive?

  在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。

  在后来的HTTP/1.0中以及HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,下一次咱们还用这个请求继续交流。

  协议规定HTTP/1.0如果想要保持长连接,需要在请求头中加上Connection: keep-alive。而HTTP/1.1默认是支持长连接的,有没有这个请求头都行。

  使用keep-alive可以改善每次需要创建连接的这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。


2 非长链接和长链接的图示


 

 

3 keep-alive的优缺点

3.1 优点

  1)节省了服务端 CPU 和内存适用量

  2)降低拥塞控制 (TCP 连接减少)

  3)减少了后续请求的延迟(无需再进行握手)

 

3.2 缺点

  对于某些低频访问的资源 / 服务,比如一个冷门的图片服务器,一年下不了几次,每下一次连接还保持就比较浪费了(这个场景举的不是很恰当)。Keep-Alive 可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数

 

4 连接复用后会有什么问题

  在没有连接复用时,Http 接收端(注意这里是接收端,并没有特指 Client/Server,因为 Client/Server 都同是发送端和接收端)只需要读取 Socket 中所有的数据就可以了,解决 “拆包” 问题即可;但是连接复用后,无法区分单次 Http 报文的边界,所以还需要额外处理报文边界问题。当然这个通过 Http 中 Header 的长度字段,按需读取即可解决

5 Http 连接复用后包边界问题处理

  由于 Http 中 Header 的存在,通过定义一些报文长度的首部字段,可以很方便的处理包边界问题。

  在 Http 中,有两种方式处理包边界问题。

 

5.1 Content-Length 处理包边界


  这个是最通常的处理方式,接收端处理报文时首先读取完整首部(Header),然后通过 Header 中的Content-Length来确认报文大小,读取报文时按此长度读取即可,超出长度的报文(“粘包”)不读取,不够长度的报文缓存等待继续读取(“拆包”)。

 

5.2 Chunked 处理包边界


  对于无法确认总报文大小的情况,可以使用 Chunked 的方式来对报文进行分块传输,每一块内标示报文大小。比如 Nginx,开启 Gzip 压缩后,就会开启 Chunked 的传输方式。

  通过 Wireshark 抓包,可以很直观的看初 Chunked 的原理:

  注意,这里的 chunk 包,和 tcp segment 不是一回事,chunk 只是应用层的一个分包,而 tcp 的 segment 是对应用层报文再次进行分组

  每个 chunk 报文前,会携带当前 chunk 的大小

  

 

6 Http 连接复用后怎样断开连接

  通过 Keep-Alive 已经做到连接复用了,但复用之后什么时候断开连接呢,不然一直保持连接,造成资源的浪费。

Http 协议规定了两种关闭复用连接的方式:

 

6.1 通过 Keep-Alive Timeout 标识


  如果服务端 Response Header 设置了Keep-Alive:timeout={timeout},客户端会就会保持此连接 timeout(单位秒)时间,超时之后关闭连接。

 

6.2 通过 Connection close 标识

  还有一种方式是接收端通在 Response Header 中增加Connection close标识,来主动告诉发送端,连接已经断开了,不能再复用了;客户端接收到此标示后,会销毁连接,再次请求时会重新建立连接。

  注意:配置 close 配置后,并不是说每次都新建连接,而是约定此连接可以用几次,达到这个最大次数时,接收端就会返回 close 标识

7 nginx的keepalive相关配置

  官方文档:https://nginx.org/en/docs/http/ngx_http_upstream_module.html

 

7.1  nginx保持keepalive做的事情

  1)client到nginx的连接是长连接

  2)nginx到server的连接是长连接

 

7.2 配置TCP层keepalive探活机制的三个参数

#情况1:
http {
server {
    listen 127.0.0.1:3306 so_keepalive=on;#开启keepalive探活,探测策略走系统默认
    }
}
#情况2:
http {
server {
    listen 127.0.0.1:3306 so_keepalive=7m:75s:9;#把空闲时长从系统默认的5分钟改为了7分钟
    }
}

其中so_keepalive有如下选择配置

so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]
*   on: 开启,探测参数更加系统默认值
*   off: 关闭
*   keepidle: 连接空闲等待时间 
*   keepintvl: 发送探测报文间隔时间
*   keepcent: 探测报文重试次数

如果nginx未设置so_keepalive配置,则走系统默认的探活策略

 

7.3 nginx与客户端(一般为浏览器、APP等)保持的长连接进行限制管理

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

  keepalive_timeout:客户端连接在服务器端空闲状态下保持的超时值(默认75s);值为0会禁用keep-alive,也就是说默认不启用长连接;第二个参数:响应的header域中设置“Keep-Alive: timeout=time”;告知浏览器对长连接的维持时间

  keepalive_requests:默认100,某个长连接连续处理请求次数限制,超过次数则该长连接被关闭;如果需要释放某个连接占用的内存,必须关闭该链接,内存不大的情况下,不建议开大该配置;在QPS较高的场景,则有必要加大这个参数

 

7.4 nginx与上游server保持长连接

http {
    upstream  BACKEND {
        server 127.0.0.1:8000;
        server 127.0.0.1:8001;
        server 127.0.0.1:8002;
        keepalive 300; //空闲连接数   
        keepalive_timeout  120s;//与上游空闲时间
        keepalive_requests 100;//与上游请求处理最大次数
    }
    server{
        listen 8080;
        location /{
            proxy_pass http://BACKEND;
            proxy_http_version 1.1;
            proxu_set_header Connection "";
        }
    }
}

  keepalive:限制nginx某个worker最多空闲连接数,此处不会限制worker与上游服务长连接的总数;
  keepalive_timeout:nginx与上游长连接最大空闲时间,默认值为60s;
  keepalive_requests:nginx与上游长连接最大交互请求的次数,默认值为100;