文档章节

HTTP 长连接的那些事

编走编想
 编走编想
发布于 2017/04/06 20:33
字数 3202
阅读 1361
收藏 8

前言

本文更多地面向后端开发同学。虽然在后端开发中,服务间调用已经广泛采用 RPC 技术。但是由于一些原因,例如像 Dubbo 停止维护、各厂自己的 RPC 框架还存在这样那样的问题、Open API 场景的大量存在等原因,HTTP 协议在后端开发中依然被广泛采用。为了提高 HTTP 接口的调用效率,HTTP 长连接是常见的优化手段。因此本文选择介绍一下 HTTP 和 TCP 长连接这个老生常谈的话题。

本文介绍了 TCP 和 HTTP Keep Alive 的相关概念和知识,以及常用的 HTTP 客户端、服务器技术在 Keep Alive 方面相关的配置。目的在于帮助大家正确地理解相关概念,用好相关技术。

本文提到的示例代码和 TCPDUMP 文件可以在这里找到。

下文中的 Keep Alive 与长连接指同一概念,Keep Alive 是通用的称谓,但为了方便交流,有时会使用长连接

TCP 长连接 (Keep Alive)

TCP 长连接是一种保持 TCP 连接的机制。当一个 TCP 连接建立之后,启用 TCP Keep Alive 的一端便会启动一个计时器,当这个计时器到达 0 之后,一个 TCP 探测包便会被发出。这个 TCP 探测包是一个纯 ACK 包,但是其 Seq 与上一个包是重复的。

打个比喻,TCP Keep Alive 是这样的:

TCP 连接两端好比两个人,这两个人之间保持通信往来(建立 TCP 连接)。如果他俩经常通信(经常发送 TCP 数据),那这个 TCP 连接自然是建立着的。但如果两人只是偶尔通信。那么,其中一个人(或两人同时)想知道对方是否还在,就会定期发送一份邮件(Keep Alive 探测包),这个邮件没有实质内容,只是问对方是否还在,如果对方收到,就会回复说还在(对这个探测包的 ACK 回应)。

需要注意的是,keep alive 技术只是 TCP 技术中的一个可选项。因为不当的配置可能会引起诸如一个正在被使用的 TCP 连接被提前关闭这样的问题,所以默认是关闭的

HTTP 长连接 (Keep Alive)

在 HTTP 1.0 时期,每个 TCP 连接只会被一个 HTTP Transaction(请求加响应)使用。之后,这个 TCP 连接便会被关闭。当网页内容越来越复杂,包含大量图片、CSS 等资源之后,这种模式效率就显得太低了。所以,在 HTTP 1.1 中,引入了 HTTP persistent connection 的概念,也称为 HTTP keep-alive(后面统一称呼为 HTTP 长连接)。

HTTP 1.0 和 1.1 在 TCP 连接使用方面的差异可见下图:

image

当需要建立 HTTP 长连接时,HTTP 请求头将包含如下内容:

Connection: Keep-Alive

如果服务端同意建立长连接,HTTP 响应头也将包含如下内容:

Connection: Keep-Alive

当需要关闭连接时,HTTP 头中会包含如下内容:

Connection: Close

也打个比方:

两个人互相通信,一个人发信,并附上一句,我们保持联系好吗。另一个人在回信中写到,好的,我们继续保持联系。此后,他们每封信里都写上相同的内容。直到有一天,友谊的小船翻了,一个人在回信里写到,我们不要再联系了。另一个人信守承诺,回道:好的,我们不联系了。从此两人天各一方。。。

TCP Keep Alive 与 HTTP Keep Alive 的关系

如上文的解释,TCP Keep Alive 和 HTTP Keep Alive 是两个目的不同的技术,不存在谁依赖于谁的关系。TCP Keep Alive 用于探测对端是否存在,而 HTTP Keep Alive 用于协商以复用 TCP 连接。即便一个 TCP 连接未启用 Keep Alive 功能,也不妨碍 HTTP 层面开启长连接。

但如果 TCP Keep Alive 的 interval 数值设置果断,就可能导致 HTTP 无法重复利用已建立的 TCP 连接。

HTTP 客户端配置

Apache HTTP Client

Apache HTTP Client 存在两种版本,在 3.x 版本时被称为 Commons HttpClient,4.x 后 Apache 创建了一个新的独立项目:Apache HttpComponents。这里指的是后者。

话不多说,看代码:

HttpClient buildHttpClient() {
    PoolingHttpClientConnectionManager connectionManager =
            new PoolingHttpClientConnectionManager(2, TimeUnit.MINUTES);
    connectionManager.setMaxTotal(DEFAULT_MAX_PER_ROUTE * 2);
    connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);

    HttpClientBuilder builder = HttpClients.custom()
            .setConnectionManager(connectionManager);

    return builder.build();
}

上面这段代码展示了如何创建一个使用连接池的 HttpClient 的方法。当使用连接池后,HttpClient 就不必为每个请求创建一个单独的 TCP 连接了。

OkHttp

OkHttp 是国外的互联网支付公司 Square 的产品,优点是比较轻量,主要面向 Android 开发 使用,也可以使用在服务端开发场景中。使用 OkHttp 设置 HTTP 长连接比较简单,默认就会开启。只需创建一个 OkHttpClient 即可。

OkHttpClient client = new OkHttpClient();

这个 client 应该是单例的,因为它有自己的连接池。

不同于 Apache HTTP Client 的连接池,OkHttp 的连接池无法设置开启最大的连接数。有多少个线程使用 OkHttpClient 发送请求,其就会创建多少个连接。

OkHttp 实现 HTTP 连接池的组件是 ConnectionPool。虽然其不能设置最大连接数,但是可以设置最大空闲连接数和连接超时时间。

RestTemplate

RestTemplate 是 Spring 框架提供的 HTTP 客户端组件,适合调用 RESTful API 的场景。其并没有直接实现 HTTP 客户端功能,而是利用现有的组件。下面仅给出 RestTemplate 使用 Apache HTTP Client 时如何配置连接池:

@Profile("apache")
@Bean
public ClientHttpRequestFactory apacheHttpClientFactory() {
    return new CustomHttpComponentsClientHttpRequestFactory();
}

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(clientHttpRequestFactory);
    return restTemplate;
}

static class CustomHttpComponentsClientHttpRequestFactory extends
        HttpComponentsClientHttpRequestFactory {
    CustomHttpComponentsClientHttpRequestFactory() {
        setHttpClient(buildHttpClient());
    }

    private HttpClient buildHttpClient() {
        PoolingHttpClientConnectionManager connectionManager =
                new PoolingHttpClientConnectionManager(2, TimeUnit.MINUTES);
        connectionManager.setMaxTotal(CONN_POOL_SIZE * 2);
        connectionManager.setDefaultMaxPerRoute(CONN_POOL_SIZE);

        HttpClientBuilder builder = HttpClients.custom()
                .setConnectionManager(connectionManager);

        return builder.build();
    }
}

操作系统设置

因为服务器操作系统很少采用 Windows,所以这里之谈 Linux 的相关配置。

在 Linux 操作系统中,TCP Keep Alive 相关的配置可以在 /proc/sys/net/ipv4/ 目录中找到,具体有下面三个(括号中的是默认值):

  • tcp_keepalive_time (7200)
  • tcp_keepalive_intvl (75)
  • tcp_keepalive_probes (9)

tcp_keepalive_time 的含义是在空闲相应时间(单位是秒)之后,TCP 协议栈将发送 Keep Alive 探测包;tcp_keepalive_intvl 的含义是开始探测之后每个探测包所间隔的时长,单位同样是秒;tcp_keepalive_probes 是探测包的个数。

默认的配置通常不适合一般的 Web 服务器,实际参数会远小于上述默认值。

正如前文所说,TCP Keep Alive 是用来检测 TCP 连接有效性的一种机制,如果一个空闲的 TCP 连接如果失效,那究竟多久能发现?假设采用如下配置:

  • tcp_keepalive_time = 60
  • tcp_keepalive_intvl = 10
  • tcp_keepalive_probes = 6

那将在 60 + 10 * 6 = 120s,即两分钟之后发现一个失效的 TCP 连接。

Nginx Keep Alive 配置

相较于有着丰富选择的 HTTP 客户端,在负载均衡服务器方面选择不是那么多,Nginx 是较为常见的选择。下面一段是启用了 HTTP 长连接的配置:

events {
    use                 epoll;
    worker_connections  102400;
}

http {
    upstream keepAliveService {
        server 10.10.131.149:8080;
        keepalive 20;
    }

    server {
        listen 80;
        server_name keepAliveService;
        location /keep-alive/hello {
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_pass http://keepAliveService;
        }
    }
}

上面这段配置中,对于 HTTP 长连接来说至关重要的有这么几项:

  1. keepalive
  2. proxy_http_verion
  3. proxy_set_header

如果想要启用 HTTP 长连接,那这三项配置都是必须的。下面分别介绍。

keepalive

顾名思义,这个参数用于控制可连接整个 upstream servers 的 HTTP 长连接的个数,即控制总数。但需要注意的是,这个参数并不控制所有 upstream servers 连接的个数。

proxy_http_verion

这个用于控制代理后端链接时使用的 HTTP 版本,默认为 1.0。要想使用长连接,必须配置为 1.1。除了 location 以外,这项配置还可以放在 http 和 server 这两级中。

proxy_set_header

除了要将 HTTP 协议设置为 1.1 以外,还要清空 Connection header 中的值。如果不配置 proxy_set_header Connection "",则发往 upstream servers 的请求中,Connection header 的值将为 close,导致无法建立长连接。

proxy_http_version 一样,这项配置也可以放在 http 和 server 这两级中。

应用服务器配置

因为本文的示例程序使用 Spring Boot(版本为 1.5.2)开发,所以介绍 Spring Boot 中的两种应用服务:Tomcat 和 Undertow。

Tomcat

Spring Boot 使用的 Tomcat 版本为 8.5.11,在使用默认配置情况下,每一个 HTTP 长连接会保持 10s 中,10s 之后,Tomcat 会主动断开连接。

从 HTTP 消息内容来看,虽然发往 Tomcat 的 HTTP 请求中带有 Connection: Keep-Alive,但是 Tomcat 提供的相应中并没有带有 Connection: Keep-Alive header。所以,严格来说,HTTP 长连接并没有建立成功(响应中没有包含 Connection: Keep-Alive 说明服务器端并没有同意建立长连接)。

但是,与 Tomcat 建立的连接确实被复用了,只是没有持续太长时间(10s)

Undertow

Undertow 是 JBoss 新推出的 Web 容器,特点在于高性能,有着远好于 Tomcat 的性能。

在对 HTTP 长连接支持方面,Undertow 的行为同 Tomcat 不同,不会主动关闭 HTTP 连接,也减少了建立、关闭 TCP 连接所带来的性能消耗。

长连接与短连接的性能对比

追求更好的性能通常是使用 HTTP 长连接的目的,那采用 HTTP 长连接之后,究竟有哪些性能提高?

接口响应时间

测试使用 JDK 8 编写的应用,应用服务器采用 Undertow,接口使用 Spring MVC 开发,接口耗时 100ms(Sleep 100ms),TPS 为 1000 左右,HTTP 客户端采用 Apache HTTP Client,配置 100 个连接的连接池,经过 Nginx 转发。非长连接模式是在 Nginx 配置中将 proxy_set_header Connection "" 去掉。

经过三次测试,在接口平均响应时间方面,使用长连接比不使用长连接少 1ms。

采用长连接:

c.github.yanglifan.KeepAliveApplication  : Average time is 106

不采用长连接:

c.github.yanglifan.KeepAliveApplication  : Average time is 107

可见,在平均响应时间方面,长连接和短连接差距很小。

服务器负载

虽然接口平均响应时间方面差距不大,但是在服务器负载方面,应用长连接确有实实在在的好处。以 Nginx 服务器为例

使用长连接时:

image

不使用长连接时:

image

平均来看,在上述负载的情况下,使用长连接时 Nginx 的 CPU 占用率为 5% 左右,不使用长连接时为 9% 左右,可见差距还是比较明显的。也说明了使用长连接的好处,就是通过避免服务器频繁建立连接,降低服务器的负载。

附:Linux 命令

netstat 命令

netstat 是常用的用来查看网络相关数据的命令,常用的参数有 anp。这三个参数比较常用,不在复述其用途。

不太常用的参数由 o,o 的意思是显示 TCP Timers。当增加 o 这个参数时,可能看到的结果是这样的:

keepalive (6176.47/0/0)  

也可能是这样的

off (0.00/0/0)

当为 keepalive 时,表明 TCP 协议栈开启了 keep alive timers,后面括号中的值分别是 timer 所剩时长/重传次数/keepalive probe 次数。当为 off 时,表明没有开启 TCP keepalive timer。当然,正如前面所说,这个 timer 是否开启,和 HTTP keep alive 没有必然联系。

watch 命令

当查看服务器和客户端之间是否启用 HTTP keep alive 时,除了抓包以外,更为快速的方法是使用 netstat 命令看连接两次的端口是否不变。如果客户端端口一直不变,说明服务端和客户端之间一直保持着同一个连接。

但是,手工不停执行 netstat 命令不是个好办法,这里就要清楚 watch 命令。

watch 命令可以实现命令的反复执行,示例如下:

watch -n1 "netstat -anpo | grep :8080 | grep EST | grep 10.110.24.202"

引号内的便是需要执行的命令。-n 的参数用来指定重复执行的间隔,默认为 2秒。

总结

总结一下,首先 HTTP 和 TCP 的长连接(keep alive)是不同的机制,之间没有必然联系。而在 HTTP 方面,不同的客户端、服务器端产品的行为也是不同的:Apache HTTP Client 可以通过配置连接池、OkHttp 同样可以使用连接池,但是可配置的参数不多、RestTemplate 基于其它 HTTP Client 的实现;Nginx 默认不开启 HTTP 长连接,也需要配置,使用 HTTP 长连接可以降低 Nginx 负载;应用服务器对 HTTP 长连接的行为也有不同,需要分别对待。

© 著作权归作者所有

共有 人打赏支持
编走编想
粉丝 141
博文 126
码字总数 107958
作品 0
海淀
程序员
加载中

评论(1)

栋感超人ing
栋感超人ing
谢谢作者,一直对长连接这个概念比较疑惑,网上的文章的作者估计好多自己都没理清,今天看了您的文章对概念初步明确了。:bowtie:
关于HTTP的长连接和短连接那些事

首先这里简单提及一下HTTP协议,HTTP协议是位于应用层面向对象的协议,现在WWW中使用的是HTTP/1.1版本,关于HTTP/1.0也是今天要说的内容。 HTTP1.1比特HTTP1.0相比而言,最大的区别就是增加了...

暮回_梓
06/26
0
0
http的长连接和短连接(史上最通俗!)

1.以前的误解 很久之前就听说过长连接的说法,而且还知道HTTP1.0协议不支持长连接,从HTTP1.1协议以后,连接默认都是长连接。但终究觉得对于长连接一直懵懵懂懂的,有种抓不到关键点的感觉。...

熊师傅
2017/10/30
0
0
误人子弟的网络,谈谈HTTP协议中的短轮询、长轮询、长连接和短连接

引言      最近刚到公司不到一个月,正处于熟悉项目和源码的阶段,因此最近经常会看一些源码。在研究一个项目的时候,源码里面用到了HTTP的长轮询。由于之前没太接触过,因此LZ便趁着这个...

zuoxiaolong8810
2017/03/23
0
0
androidpn 推送初探

androidpn是基于XMPP协议的用于向Android客户端推送文本信息的一套开源的工具。它帮我们做了那些维护Socket长连接等等的事情。 在真正把它使用在我们的项目中之前,我们先领略一下推送。 第一...

程序袁_绪龙
2014/11/24
0
0
AI 时代,爱学习的程序员都关注了这些…

这篇文章推荐了包括技术、设计、极客相关的热门公众号。

p5deyt322jacs
01/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux 中不适用功能键切换TTY

本简要指南介绍了在类 Unix 操作系统中如何在不使用功能键的情况下切换 TTY。在进一步讨论之前,我们将了解 TTY 是什么。正如在 AskUbuntu 论坛的一个答案[1]中所提到的,TTY这个词来自 Tele...

问题终结者
5分钟前
0
0
OSChina 周三乱弹 —— 我自己总觉得我的灵魂有毒

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @Devoes :分享王菲的单曲《匆匆那年 (Fleet of Time)》 《匆匆那年 (Fleet of Time)》- 王菲 手机党少年们想听歌,请使劲儿戳(这里) 天长地...

小小编辑
12分钟前
3
3
深度学习与图像处理实例:人像背景虚化与背景替换

简单人像背景虚化处理思路如下: 对图像内容分割,提取人像,背景 背景模糊处理 人像与模糊处理后的背景融合 本实例使用DeepLabV3图像分割深度学习模型实现。代码如下: import numpy as np...

IOTService
昨天
0
0
八月新增开源项目:假装自己是图形界面的 Git 命令行工具

每月新增开源项目。顾名思义,每月更新一期。我们会从社区上个月新收录的开源项目中,挑选出有价值的、有用的、优秀的、或者好玩的开源项目来和大家分享。数量不多,但我们力求推荐的都是精品...

编辑部的故事
昨天
7
0
20180918 find命令与Linux文件扩展名

命令find 用来查找搜索文件。 搜索文件相关命令: which 从环境变量里的目录中去搜索 whereis(不常用) 从一个固定的库中搜索 locate(需要单独安装 yum install -y mlocate) 查询时会从/var/...

野雪球
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部