• http 请求 —— 连接数问题 & httpclient 重用


    原文:

    https://blog.csdn.net/haicunzaima/article/details/84529735

    【问题现象】

    系统上线后出现TCP连接数超过预期阀值,最高值达到8K左右,新上线代码中包含了一文件上传操作,使用的是apache的commons-httpclient包。

    【问题分析】

    1、先确认是否存在连接未关闭问题引起的。

    观察发现,TCP连接数不是一直在增长,而是会有所下降。并且当业务低峰期TCP连接数TCP连接数会降到100左右,这说明TCP连接还是会关闭。

    2、确定居高不下的TCP使用情况

    使用"netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'"命令发现,处于ESTABLISHED状态的连接数最多,在查看了一下处于ESTABLISHED状态的目的IP,基本上都是文件服务器的IP,这说明还是跟新增加的文件上传操作有关。但是按照代码的逻辑来看,文件上传操作是多线程处理的,一个线程处理一个上传操作,线程池中一共有10个线程,照此分析正常的话应该有10个左右与文件服务器的链接,不应该出现几千个链接。因此怀疑是连接没有主动释放,而是等待连接超时才开始释放。

    3、为什么会连接超时

    查看了文件上传部分代码,主要代码如下:

    HttpClient client = new HttpClient();
    MultipartPostMethod method = new MultipartPostMethod(config .getUploadInterface());
    try{
    client.executeMethod(method);
    }catch (Exception e){
    throw e;
    }finally{
    method.releaseConnection();
    }
    从代码里看是已经释放连接了,但是从结果上看没有释放连接,那就产生一个问题,这个地方真的能释放连接吗?我们在释放连接后面增加一行测试代码来看看:

    HttpConnection conn = client.getHttpConnectionManager().getConnection(client.getHostConfiguration());
    System.out.println(conn.isOpen());
    打印出的结果是true,也就是说虽然调用了releaseConnection,但是并没有释放连接!!

    4、分析commons-httpclient相关代码

    现在怀疑是我们使用的方式不对了,继续分析一下commons-https包中相关代码,首先看一下method.releaseConnection()的代码实现:

    public void releaseConnection() {
    try {
    if (this.responseStream != null) {
    try {
    // FYI - this may indirectly invoke responseBodyConsumed.
    this.responseStream.close();
    } catch (IOException ignore) {
    }
    }
    } finally {
    ensureConnectionRelease();
    }
    }

    private void ensureConnectionRelease() {
    if (responseConnection != null) {
    responseConnection.releaseConnection();
    responseConnection = null;
    }
    }
    经过debug发现responseStream为null,并且responseConnection也为null,这样改调用就没有实际意义。那么我们应该怎么来释放连接呢?

    5、继续分析代码

    我们发现在org.apache.commons.httpclient.HttpMethodDirector类的第208行已经在finally中释放连接了:

    finally {
    if (this.conn != null) {
    this.conn.setLocked(false);
    }
    // If the response has been fully processed, return the connection
    // to the pool. Use this flag, rather than other tests (like
    // responseStream == null), as subclasses, might reset the stream,
    // for example, reading the entire response into a file and then
    // setting the file as the stream.
    if (
    (releaseConnection || method.getResponseBodyAsStream() == null)
    && this.conn != null
    ) {
    this.conn.releaseConnection();
    }
    }

    public void releaseConnection(HttpConnection conn) {
    if (conn != httpConnection) {
    throw new IllegalStateException("Unexpected release of an unknown connection.");
    }

    finishLastResponse(httpConnection);

    inUse = false;

    // track the time the connection was made idle
    idleStartTime = System.currentTimeMillis();
    }
    这个地方我们可以看到了所谓的释放连接并不是真的释放,还是return the connection to pool,照此分析,我们每个线程中new了一个HttpClient类,而每个HttpClient类中的链接都是没有close的,只是归还到httpClient中的pool而已,这些连接也必须等到连接超时才会被释放,由此可以分析出来连接数上涨的原因。那么我们应该怎么使用呢?按照代码的设计,看起来httpclient应该是单例的,但是在httpClient类的javadoc中并没有关于线程安全方面的说明,为此我们再回到官网上看相关文档,在文档(http://hc.apache.org/httpclient-3.x/performance.html)上我们看到如下的说明:

    HttpClient is fully thread-safe when used with a thread-safe connection manager such as MultiThreadedHttpConnectionManager
    这说明在多线程环境下应该使用一个全局单例的HttpClient,并且使用MultiThreadHttpConnectionManager来管理Connection。

    【相关结论】

    1、HttpClient内部使用了池化技术,内部的链接是为了复用。在多线程条件下,可以使用一个全局的HttpClient实例,并且使用MultiThreadHttpConnectionManager来管理Connection。

    2、使用开源软件之前一定要读读相关代码,看看官方推荐使用方式。

    3、在解决此问题后,读了读httpclient中其他包中的代码,在读的时候发现对于理解http协议帮助很大,特别是文件上传,长连接,auth鉴权等。

    https://q.cnblogs.com/q/90727/

    你想用多个HttpClient又不想让多个TCP。这个是悖论,不可能的。

    官方推荐是一个域使用一个HttpClient。

    按你所说,你的header一直变动是不应该的,资源条件应该放在url里面,数据应该放在content里面,你header来回变动是做什么用的呢?即使变动也无非是header变动。

    你在找一种能让你的错误的使用方法变得不出错的办法……

     你的意思是Reponse回复的时候带Header么?发送的时候,Header你尽管改,没有问题的。假设这样一个场景,你Header目前是h1,你r1在请求的时候用的是h1,此时,r2要请求,计划也用h1,但是,同时你r3要用h2请求,导致你的r2用的不是h1而是和r3一样都用的是h2。你的意思是担心这样的问题么?

    你同一个域下,一个请求应该是可以一直或者在将来的持续一段时间内都生效可重复的,否则幂等性就没有了。票据正常也是放在Auth里面的,也是放在头中,所以,你的操作没有问题。只是,你的票据不应该每次都变的吧,如果每次都变,你并发都做没办法并发。

    如果过期——说明,不变是常态,但是在一定情况下会变,这种情况,你直接改就可以了呀。

    https://blog.csdn.net/weixin_39528029/article/details/114124727?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-3-114124727.pc_agg_new_rank&utm_term=httpclient%E5%A4%8D%E7%94%A8tcp&spm=1000.2123.3001.4430

    https://blog.csdn.net/weixin_39585795/article/details/110718221?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.pc_relevant_paycolumn_v2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.pc_relevant_paycolumn_v2&utm_relevant_index=1

    • 事件背景
    • 问题过程
    • 案情回顾
    • 深入排查
    • 案情总结

    事件背景

    我在凤巢团队独立搭建和运维的一个高流量的推广实况系统,是通过HttpClient 调用大搜的实况服务。最近经常出现Address already in use (Bind failed)的问题。很明显是一个端口绑定冲突的问题,于是大概排查了一下当前系统的网络连接情况和端口使用情况,发现是有大量time_wait的连接一直占用着端口没释放,导致端口被占满(最高的时候6w+个),因此HttpClient建立连接的时候会出现申请端口冲突的情况。

    具体情况如下:

    c40747b7e6f5bee2010feb9353d1ebb7.png

    于是为了解决time_wait的问题,网上搜索了些许资料加上自己的思考,于是认为可以通过连接池来保存tcp连接,减少HttpClient在并发情况下随机打开的端口数量,复用原来有效的连接。但是新的问题也由连接池的设置引入了。

    问题过程

    在估算连接池最大连接数的时候,参考了业务高峰期时的请求量为1分钟1.2w pv,接口平响为1.3s(复杂的广告推广效果模拟系统,在这种场景平响高是业务所需的原因),因此qps为12000*1.360=260

    然后通过观察了业务日志,每次连接建立耗时1.1s左右, 再留70%+的上浮空间(怕连接数设置小出系统故障),最大连接数估计为2601.11.7约等于500

    为了减少对之前业务代码最小的改动,保证优化的快速上线验证,仍然使用的是HttpClient3.1 的MultiThreadedHttpConnectionManager,设置核心代码如下:

    public void init() { connectionManager = new MultiThreadedHttpConnectionManager(); HttpConnectionManagerParams managerParams = new HttpConnectionManagerParams(); managerParams.setMaxTotalConnections(500); // 最大连接数 connectionManager.setParams(managerParams); client = new HttpClient(connectionManager);}

    然后在线下手写了多线程的测试用例,测试了下并发度确实能比没用线程池的时候更高,然后先在我们的南京机房小流量上线验证效果,效果也符合预期之后,就开始整个北京机房的转全。结果转全之后就出现了意料之外的系统异常。。。

    情回顾

    在当天晚上流量转全之后,一起情况符合预期,但是到了第二天早上就看到用户群和相关的运维群里有一些人在反馈实况页面打不开了。这个时候我在路上,让值班人帮忙先看了下大概的情况,定位到了耗时最高的部分正是通过连接池调用后端服务的部分,于是可以把这个突发问题的排查思路大致定在围绕线程池的故障来考虑了。

    于是等我到了公司,首先观察了一下应用整体的情况:

    1. 监控平台的业务流量表现正常,但是部分机器的网卡流量略有突增
    2. 接口的平响出现了明显的上升
    3. 业务日志无明显的异常,不是底层服务超时的原因,因此平响的原因肯定不是业务本身
    4. 发现30个机器实例竟然有9个出现了挂死的现象,其中6个北京实例,3个南京实例

    深入排查

    由于发现了有近 1/3的实例进程崩溃,而业务流量没变,由于RPC服务对provider的流量进行负载均衡,所以引发单台机器的流量升高,这样会导致后面的存活实例更容易出现崩溃问题,于是高优看了进程挂死的原因。由于很可能是修改了HttpClient连接方式为连接池引发的问题,最容易引起变化的肯定是线程和CPU状态,于是立即排查了线程数和CPU的状态是否正常。

    CPU状态

    如图可见Java进程占用cpu非常高,是平时的近10倍。

    b5d69c37ce29a2f8eab495f80f914ee3.png

    线程数监控状态

    7a908bf120239e84c143dccb07ef56a5.png
    b05b20e45ed7a4d1335b69824c3f1be3.png

    图中可以看到多个机器大概在10点初时,出现了线程数大量飙升,甚至超出了虚拟化平台对容器的2000线程数限制(平台为了避免机器上的部分容器线程数过高,导致机器整体夯死而设置的熔断保护),因此实例是被虚拟化平台kill了。之前为什么之前在南京机房小流量上线的时候没出现线程数超限的问题,应该和南京机房流量较少,只有北京机房流量的1/3有关。

    回滚之前tcp连接情况

    920c798a1eeb511d7e6cb68b43312dce.png

    回滚之后tcp连接情况

    287a477ab648d85cd636cc8745fefa93.png

    发现连接线程的并发度果然小很多了,这个时候要再确认一下是否是连接池设置导致的原因,于是将没回滚的机器进行jstack了,对Java进程中分配的子线程进行了分析,终于可以确认问题。

    jstack状态

    7ffb4eacb710cf7b000ece5ba4fd973d.png

    从jstack的日志中可以很容易分析出来,有大量的线程在等待获取连接池里的连接而进行排队,因此导致了线程堆积,因此平响上升。由于线程堆积越多,系统资源占用越厉害,接口平响也会因此升高,更加剧了线程的堆积,因此很容易出现恶性循环而导致线程数超限。

    那么为什么会出现并发度设置过小呢?之前已经留了70%的上浮空间来估算并发度,这里面必定有蹊跷!

    于是我对源码进行了解读分析,发现了端倪:

    85e0cda63a9a1ca0e5b4bbb9595d01f6.png

    如MultiThreadedHttpConnectionManager源码可见,连接池在分配连接时调用的doGetConnection方法时,对能否获得连接,不仅会对我设置的参数maxTotalConnections进行是否超限校验,还会对maxHostConnections进行是否超限的校验。

    于是我立刻网上搜索了下maxHostConnections的含义:每个host路由的默认最大连接,需要通过setDefaultMaxConnectionsPerHost来设置,否则默认值是2。

    所以并不是我对业务的最大连接数计算失误,而是因为不知道要设置DefaultMaxConnectionsPerHost而导致每个请求的Host并发连接数只有2,限制了线程获取连接的并发度(所以难怪刚才观察tcp并发度的时候发现只有2个连接建立 )

    案情总结

    到此这次雪崩事件的根本问题已彻底定位,让我们再次精炼的总结一下这个案件的全过程:

    • 连接池设置错参数,导致最大连接数为2
    • 大量请求线程需要等待连接池释放连接,出现排队堆积
    • 夯住的线程变多,接口平响升高,占用了更多的系统资源,会加剧接口的耗时增加和线程堆积
    • 最后直至线程超限,实例被虚拟化平台kill
    • 部分实例挂死,导致流量转移到其他存活实例。其他实例流量压力变大,容易引发雪崩。

    关于优化方案与如何避免此类问题再次发生,我想到的方案有3个:

    • 在做技术升级前,要仔细熟读相关的官方技术文档,最好不要遗漏任何细节
    • 可以在网上找其他可靠的开源项目,看看别人的优秀的项目是怎么使用的。比如github上就可以搜索技术关键字,找到同样使用了这个技术的开源项目。要注意挑选质量高的项目进行参考
    • 先在线下压测,用控制变量法对比各类设置的不同情况,这样把所有问题在线下提前暴露了,再上线心里就有底了

    以下是我设计的一个压测方案:

    • 测试不用连接池和使用连接池时,分析整体能承受的qps峰值和线程数变化
    • 对比setDefaultMaxConnectionsPerHost设置和不设置时,分析整体能承受的qps峰值和线程数变化
    • 对比调整setMaxTotalConnections,setDefaultMaxConnectionsPerHost 的阈值,分析整体能承受的qps峰值和线程数变化
    • 重点关注压测时实例的线程数,cpu利用率,tcp连接数,端口使用情况,内存使用率

    综上所述,一次连接池参数导致的雪崩问题已经从分析到定位已全部解决。在技术改造时我们应该要谨慎对待升级的技术点。在出现问题后,要重点分析问题的特征和规律,找到共性去揪出根本原因。

    https://blog.csdn.net/weixin_39528029/article/details/114124727?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-3-114124727.pc_agg_new_rank&utm_term=httpclient%E5%A4%8D%E7%94%A8tcp&spm=1000.2123.3001.4430

    最近修改同事代码时遇到一个问题,通过 httpclient 默认配置产生的 httpclient 如果不关闭,会导致连接无法释放,很快打满服务器连接(内嵌 Jetty 配置了 25 连接上限),主动关闭问题解决;后来优化为通过连接池生成 httpclient 后,如果关闭 httpclient 又会导致连接池关闭,后面新的 httpclient 也无法再请求,这里总结遇到的一些问题和疑问。

    官网示例中的以下三个 close 分别释放了什么资源,是否可以省略,以及在什么时机调用,使用连接池时有区别么?

    作为 RPC 通信客户端,如何复用 TCP 连接?

    一、资源释放

    CloseableHttpClient httpclient = HttpClients.createDefault();

    HttpGet httpget = new HttpGet("http://localhost/");

    CloseableHttpResponse response = httpclient.execute(httpget);

    try {
    HttpEntity entity = response.getEntity();

    if (entity != null) {
    InputStream instream = entity.getContent();

    try {
    // do something useful

    } finally {
    instream.close();

    }

    }

    } finally {
    response.close();

    }

    // httpclient.close();

    首先需要了解默认配置 createDefault 和使用了 custom 连接池(文章最后的 HttpClientUtil)两种情况的区别,通过源码可以看到前者也创建了连接池,最大连接20个,单个 host最大2个,但是区别在于每次创建的 httpclient 都自己维护了自己的连接池,而 custom 连接池时所有 httpclient 共用同一个连接池,这是在 api 使用方面需要注意的地方,要避免每次请求新建连接池、关闭连接池,造成性能问题。

    The difference between closing the content stream and closing the response is that the former will attempt to keep the underlying connection alive by consuming the entity content while the latter immediately shuts down and discards the connection.

    第一个 close 是读取 http 正文的数据流,类似的还有响应写入流,都需要主动关闭,如果是使用 EntityUtils.toString(response.getEntity(), "UTF-8"); 的方式,其内部会进行关闭。如果还有要读/写的数据、或不主动关闭,相当于 http 请求事务未处理完成,这时通过其他方式关闭(第二个 close)相当于异常终止,会导致该连接无法被复用,对比下面两段日志。

    第一个 close 未调用时,第二个 close 调用,连接无法被复用,kept alive 0。

    o.a.http.impl.execchain.MainClientExec : Connection can be kept alive indefinitely

    h.i.c.DefaultManagedHttpClientConnection : http-outgoing-0: Close connection

    o.a.http.impl.execchain.MainClientExec : Connection discarded

    h.i.c.PoolingHttpClientConnectionManager : Connection released: [id: 0][route: {}->http://127.0.0.1:8080][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]

    第一个 close 正常调用时,第二个 close 调用,连接可以被复用,kept alive 1。

    o.a.http.impl.execchain.MainClientExec : Connection can be kept alive indefinitely

    h.i.c.PoolingHttpClientConnectionManager : Connection [id: 0][route: {}->http://127.0.0.1:8080] can be kept alive indefinitely

    h.i.c.DefaultManagedHttpClientConnection : http-outgoing-0: set socket timeout to 0

    h.i.c.PoolingHttpClientConnectionManager : Connection released: [id: 0][route: {}->http://127.0.0.1:8080][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]

    第二个 close 是强行制止和释放连接到连接池,相当于对第一个 close 的保底操作(上面关闭了这个似乎没必要了?),结合上面引用的官方文档写到 immediately shuts down and discards the connection,这里如果判断需要 keep alive 实际也不会关闭 TCP 连接,因为通过 netstat 可以看到,第二段日志后在终端可以继续观察到连接:

    # netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.51003 ESTABLISHED

    tcp4 0 0 127.0.0.1.51003 127.0.0.1.8080 ESTABLISHED

    在 SOF 上可以搜到这段话,但是感觉和上面观察到的并不相符?

    The underlying HTTP connection is still held by the response object to allow the response content to be streamed directly from the network socket. In order to ensure correct deallocation of system resources, the user MUST call CloseableHttpResponse#close() from a finally clause. Please note that if response content is not fully consumed the underlying connection cannot be safely re-used and will be shut down and discarded by the connection manager.

    第三个 clsoe,也就是 httpclient.close 会彻底关闭连接池,以及其中所有连接,一般情况下,只有在关闭应用时调用以释放资源(补充:当 httpClientBuilder.setConnectionManagerShared(true) 时,并不会关闭连接池)。

    二、连接复用

    根据 http 协议 1.1 版本,各个 web 服务器都默认支持 keepalive,因此当 http 请求正常完成后,服务器不会主动关闭 tcp(直到空闲超时或数量达到上限),使连接会保留一段时间,前面我们也知道 httpclient 在判断可以 keepalive 后,即使调用了 close 也不会关闭 tcp 连接(可以认为 release 到连接池)。为了管理这些保留的连接,以及方便 api 调用,一般设置一个全局的连接池,并基于该连接池提供 httpclient 实例,这样就不需要考虑维护 httpclient 实例生命周期,随用随取(方便状态管理?),此外考虑到 http 的单路性,一个请求响应完成结束后,该连接才可以再次复用,因此连接池的最大连接数决定了并发处理量,该配置也是一种保护机制,超出上限的请求会被阻塞,也可以配合熔断组件使用,当服务方慢、或不健康时熔断降级。

    最后还有一个问题,观察到 keepalive 的 tcp 连接过一段时间后会变成如下状态:

    # netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.51866 FIN_WAIT_2

    tcp4 0 0 127.0.0.1.51866 127.0.0.1.8080 CLOSE_WAIT

    可以看出服务器经过一段时间,认为该连接空闲,因此主动关闭,收到对方响应后进入 FIN_WAIT_2 状态(等待对方也发起关闭),而客户端进入 CLOSE_WAIT 状态后却不再发起自己这一方的关闭请求,这时双方处于半关闭。官方文档解释如下:

    One of the major shortcomings of the classic blocking I/O model is that the network socket can react to I/O events only when blocked in an I/O operation. When a connection is released back to the manager, it can be kept alive however it is unable to monitor the status of the socket and react to any I/O events. If the connection gets closed on the server side, the client side connection is unable to detect the change in the connection state (and react appropriately by closing the socket on its end).

    这需要有定期主动做一些检测和关闭动作,从这个角度考虑,默认配置产生的 HttpClient 没有这一功能,不应该用于生产环境,下面这个监控线程可以完成该工作,包含它的完整的 HttpUtil 从文章最后连接获取。

    public static class IdleConnectionMonitorThread extends Thread {
    private final HttpClientConnectionManager connMgr;

    private volatile boolean shutdown;

    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
    super();

    this.connMgr = connMgr;

    }

    @Override

    public void run() {
    try {
    while (!shutdown) {
    synchronized (this) {
    wait(30 * 1000);

    // Close expired connections

    connMgr.closeExpiredConnections();

    // Optionally, close connections

    // that have been idle longer than 30 sec

    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);

    }

    }

    } catch (InterruptedException ex) {
    // terminate

    }

    }

    最后展示一个完整的示例,首先多线程发起两个请求,看到创建两个连接,30秒之后再发起一个请求,可以复用之前其中一个连接,另一个连接因空闲被关闭,随后最后等待 2 分钟后再发起一个请求,由于之前连接已过期失效,重新创建连接。

    并发两个请求

    16:54:44.504 [ Thread-4] : Connection request: [route: {}->http://127.0.0.1:8080][total kept alive: 0; route allocated: 0 of 150; total allocated: 0 of 150]

    16:54:44.504 [ Thread-5] : Connection request: [route: {}->http://127.0.0.1:8080][total kept alive: 0; route allocated: 0 of 150; total allocated: 0 of 150]

    16:54:44.515 [ Thread-5] : Connection leased: [id: 1][route: {}->http://127.0.0.1:8080][total kept alive: 0; route allocated: 2 of 150; total allocated: 2 of 150]

    16:54:44.515 [ Thread-4] : Connection leased: [id: 0][route: {}->http://127.0.0.1:8080][total kept alive: 0; route allocated: 2 of 150; total allocated: 2 of 150]

    16:54:44.517 [ Thread-5] : Opening connection {}->http://127.0.0.1:8080

    16:54:44.517 [ Thread-4] : Opening connection {}->http://127.0.0.1:8080

    16:54:44.519 [ Thread-4] : Connecting to /127.0.0.1:8080

    16:54:44.519 [ Thread-5] : Connecting to /127.0.0.1:8080

    16:54:44.521 [ Thread-5] : Connection established 127.0.0.1:52421127.0.0.1:8080

    16:54:44.521 [ Thread-4] : Connection established 127.0.0.1:52420127.0.0.1:8080

    ....

    16:54:49.486 [ main] : [leased: 2; pending: 0; available: 0; max: 150]

    16:54:49.630 [ Thread-4] : Connection can be kept alive indefinitely

    16:54:49.630 [ Thread-5] : Connection can be kept alive indefinitely

    16:54:49.633 [ Thread-4] : Connection [id: 0][route: {}->http://127.0.0.1:8080] can be kept alive indefinitely

    16:54:49.633 [ Thread-5] : Connection [id: 1][route: {}->http://127.0.0.1:8080] can be kept alive indefinitely

    16:54:49.633 [ Thread-4] : http-outgoing-0: set socket timeout to 0

    16:54:49.633 [ Thread-5] : http-outgoing-1: set socket timeout to 0

    16:54:49.633 [ Thread-4] : Connection released: [id: 0][route: {}->http://127.0.0.1:8080][total kept alive: 1; route allocated: 2 of 150; total allocated: 2 of 150]

    16:54:49.633 [ Thread-5] : Connection released: [id: 1][route: {}->http://127.0.0.1:8080][total kept alive: 2; route allocated: 2 of 150; total allocated: 2 of 150]

    16:54:54.488 [ main] : [leased: 0; pending: 0; available: 2; max: 150]

    #netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52421 ESTABLISHED

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52420 ESTABLISHED

    tcp4 0 0 127.0.0.1.52421 127.0.0.1.8080 ESTABLISHED

    tcp4 0 0 127.0.0.1.52420 127.0.0.1.8080 ESTABLISHED

    下一个请求

    16:55:14.489 [ Thread-6] : Connection request: [route: {}->http://127.0.0.1:8080][total kept alive: 2; route allocated: 2 of 150; total allocated: 2 of 150]

    16:55:14.491 [ Thread-6] : http-outgoing-1 << "[read] I/O error: Read timed out"

    16:55:14.491 [ Thread-6] : Connection leased: [id: 1][route: {}->http://127.0.0.1:8080][total kept alive: 1; route allocated: 2 of 150; total allocated: 2 of 150]

    16:55:14.491 [ Thread-6] : http-outgoing-1: set socket timeout to 0

    16:55:14.492 [ Thread-6] : http-outgoing-1: set socket timeout to 8000

    .....

    16:55:19.501 [ main] : [leased: 1; pending: 0; available: 1; max: 150]

    16:55:19.504 [ Thread-6] : Connection can be kept alive indefinitely

    16:55:19.504 [ Thread-6] : Connection [id: 1][route: {}->http://127.0.0.1:8080] can be kept alive indefinitely

    16:55:19.505 [ Thread-6] : http-outgoing-1: set socket timeout to 0

    16:55:19.505 [ Thread-6] : Connection released: [id: 1][route: {}->http://127.0.0.1:8080][total kept alive: 2; route allocated: 2 of 150; total allocated: 2 of 150]

    16:55:24.504 [ main] : [leased: 0; pending: 0; available: 2; max: 150]

    #netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52421 ESTABLISHED

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52420 ESTABLISHED

    tcp4 0 0 127.0.0.1.52421 127.0.0.1.8080 ESTABLISHED

    tcp4 0 0 127.0.0.1.52420 127.0.0.1.8080 ESTABLISHED

    复用了上面的连接,下面是随后逐步超时的日志。

    16:55:39.513 [ main] : [leased: 0; pending: 0; available: 2; max: 150]

    16:55:44.491 [ Thread-8] : Closing expired connections

    16:55:44.492 [ Thread-8] : Closing connections idle longer than 30 SECONDS

    16:55:44.492 [ Thread-8] : http-outgoing-0: Close connection

    16:55:44.518 [ main] : [leased: 0; pending: 0; available: 1; max: 150]

    ....

    16:56:09.535 [ main] : [leased: 0; pending: 0; available: 1; max: 150]

    16:56:14.499 [ Thread-8] : Closing expired connections

    16:56:14.499 [ Thread-8] : Closing connections idle longer than 30 SECONDS

    16:56:14.499 [ Thread-8] : http-outgoing-1: Close connection

    16:56:14.540 [ main] : [leased: 0; pending: 0; available: 0; max: 150]

    分别对应状态如下,可以看到复用了 52421,随后 52420 空闲超时被回收,以及最后 52421 也被回收。

    #netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52421 ESTABLISHED

    tcp4 0 0 127.0.0.1.52421 127.0.0.1.8080 ESTABLISHED

    tcp4 0 0 127.0.0.1.52420 127.0.0.1.8080 TIME_WAIT

    ...

    #netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.52421 127.0.0.1.8080 TIME_WAIT

    最后一个请求后,日志省略,可以看到是新的连接 52443。

    netstat -n | grep tcp4 | grep 8080

    tcp4 0 0 127.0.0.1.8080 127.0.0.1.52443 ESTABLISHED

    tcp4 0 0 127.0.0.1.52443 127.0.0.1.8080 ESTABLISHED

    参考:

    相关资源:STM32F4+LWIP+HTTPclient(TCPclient)+DHCP+DNS_tcpclientlwip...

    Windows Socket 最大连接数

     

    Socket 编程时,单机最多可以建立多少个 TCP 连接,受到操作系统的影响。

    Windows 下单机的TCP连接数受多个参数影响:

    最大TCP连接数

    [HKEY_LOCAL_MACHINE \System \CurrentControlSet \Services \Tcpip \Parameters] TcpNumConnections = 0x00fffffe (Default = 16,777,214)

    以上注册表信息配置单机的最大允许的TCP连接数,默认为 16M。这个数值看似很大,这个并不是限制最大连接数的唯一条件,还有其他条件会限制到TCP 连接的最大连接数。

    最大动态端口数

    TCP客户端和服务器连接时,客户端必须分配一个动态端口,默认情况下这个动态端口的分配范围为 1024-5000 ,也就是说默认情况下,客户端最多可以同时发起3977 个Socket 连接。我们可以修改如下注册表来调整这个动态端口的范围

    [HKEY_LOCAL_MACHINE \System \CurrentControlSet \Services \Tcpip \Parameters] MaxUserPort = 5000 (Default = 5000, Max = 65534)

    最大TCB 数量

    系统为每个TCP 连接分配一个TCP 控制块(TCP control block or TCB),这个控制块用于缓存TCP连接的一些参数,每个TCB需要分配 0.5 KB的pagepool 和 0.5KB 的Non-pagepool,也就说,每个TCP连接会占用 1KB 的系统内存。

    系统的最大TCB数量由如下注册表设置决定

    [HKEY_LOCAL_MACHINE \System \CurrentControlSet \Services \Tcpip \Parameters] MaxFreeTcbs = 2000 (Default = RAM dependent, but usual Pro = 1000, Srv=2000)

    非Server版本,MaxFreeTcbs 的默认值为1000 (64M 以上物理内存)

    Server 版本,这个的默认值为 2000。

    也就是说,默认情况下,Server 版本最多同时可以建立并保持2000个TCP 连接。

    最大TCB Hash table 数量

    TCB 是通过Hash table 来管理的,下面注册表设置决定了这个Hash table 的大小

    HKEY_LOCAL_MACHINE \System \CurrentControlSet \services \Tcpip \Parameters] MaxHashTableSize = 512 (Default = 512, Range = 64-65536)

    这个值指明分配 pagepool 内存的数量,也就是说,如果MaxFreeTcbs = 1000 , 则 pagepool 的内存数量为 500KB

    那么 MaxHashTableSize 应大于 500 才行。这个数量越大,则Hash table 的冗余度就越高,每次分配和查找 TCP 连接用时就越少。这个值必须是2的幂,且最大为65536.

    参考:

    IBM WebSphere Voice Server 在windows server 2003 下的典型配置

    MaxUserPort = 65534 (Decimal)

    MaxHashTableSize = 65536 (Decimal)

    MaxFreeTcbs = 16000 (Decimal)

    这里 MaxHashTableSize 被配置为比MaxFreeTcbs 大4倍,这样可以大大增加TCP建立的速度。

     
     
     
  • 相关阅读:
    Java8新特性Function、BiFunction使用
    Java8 stream用法-备忘录
    springboot使用过滤器Filter
    dockerfile命令说明及使用
    RestTemplate对象,进行get和post简单用法
    Jackson动态处理返回字段
    springboot-jjwt HS256加解密(PS:验证就是解密)
    SpringBoot2.1.3修改tomcat参数支持请求特殊符号
    mysql存储过程 带参数 插入 操作
    性能测试如何计算设置并发数
  • 原文地址:https://www.cnblogs.com/panpanwelcome/p/15802556.html
Copyright © 2020-2023  润新知