HttpClient作为Java程序员最常用的Http工具,其对Http连接的管理能简化开发,并且提升连接重用效率;在正常情况下,HttpClient能帮助我们高效管理连接,但在一些并发高,报文体较大的情况下,如果再遇到网络波动,如何保证连接被高效利用,有哪些优化空间。
北京时间X月X日,浏览器信息流服务监控出现异常,主要表现在以下三个方面:
2. 从PAAS平台Hystrix熔断管理界面中可以进一步确认问题机器的所有Http接口调用均出现了熔断:
3. 日志中心有大量从Http连接池获取连接的异常:
org.apache.http.impl.execchain.RequestAbortedException: Request aborted。
综合以上三个现象,大概可以推测出问题机器的TCP连接管理出了问题,可能是虚拟机问题,也可能是物理机问题;与运维与系统侧沟通后,发现虚拟机与物理机均无明显异常,第一时间联系运维重启了问题机器,线上问题得到解决。
几天以后,线上部分其他机器也陆续出现了上述现象,此时基本可以确认是服务本身有问题;既然问题与TCP连接相关,于是联系运维在问题机器上建立了一个作业查看TCP连接的状态分布:
netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}'
结果如下:
如上图,问题机器的CLOSE_WAIT状态的连接数已经接近200左右(该服务Http连接池最大连接数设置的250),那问题直接原因基本可以确认是CLOSE_WAIT状态的连接过多导致的;本着第一时间先解决线上问题的原则,先把连接池调整到500,然后让运维重启了机器,线上问题暂时得到解决。
调整连接池大小只是暂时解决了线上问题,但是具体原因还不确定,按照以往经验,出现连接无法正常释放基本都是开发者使用不当,在使用完成后没有及时关闭连接;但很快这个想法就被否定了,原因显而易见:当前的服务已经在线上运行了一周左右,中间没有经历过发版,以浏览器的业务量,如果是连接使用完没有及时关。
闭,250的连接数连一分钟都撑不到就会被打爆。那么问题就只能是一些异常场景导致的连接没有释放;于是,重点排查了下近期上线的业务接口,尤其是那种数据包体较大,响应时间较长的接口,最终把目标锁定在了某个详情页优化接口上;先查看处于CLOSE_WAIT状态的IP与端口连接对,确认对方服务器IP地址。
netstat-tulnap|grep CLOSE_WAIT
经过与合作方确认,目标IP均来自该合作方,与我们的推测是相符的。
在定位问题的同时,也让运维同事帮忙抓取了TCP的数据包,结果表明确实是客户端(浏览器服务端)没返回ACK结束握手,导致挥手失败,客户端处于了CLOSE_WAIT状态,数据包的大小也与怀疑的问题接口相符。
为了方便大家理解,我从网上找了一张图,大家可以作为参考:
CLOSE_WAIT是一种被动关闭状态,如果是SERVER主动断开的连接,那么就会在CLIENT出现CLOSE_WAIT的状态,反之同理;
通常情况下,如果客户端在一次http请求完成后没有及时关闭流(tcp中的流套接字),那么超时后服务端就会主动发送关闭连接的FIN,客户端没有主动关闭,所以就停留在了CLOSE_WAIT状态,如果是这种情况,很快连接池中的连接就会被耗尽。
所以,我们今天遇到的情况(处于CLOSE_WAIT状态的连接数每天都在缓慢增长),更像是某一种异常场景导致的连接没有关闭。
为了不影响其他业务场景,防止出现系统性风险,我们先把问题接口连接池进行了独立管理。
带着2.3的疑问我们仔细查看一下业务调用代码:
try {
httpResponse = HttpsClientUtil.getHttpClient().execute(request);
HttpEntity httpEntity = httpResponse.getEntity();
is = httpEntity.getContent();
}catch (Exception e){
log.error("");
}finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(httpResponse);
}
这段代码存在一个明显的问题:既关闭了数据传输流( IOUtils.closeQuietly(is)),也关闭了整个连接(IOUtils.closeQuietly(httpResponse)),这样我们就没办法进行连接的复用了;但是却更让人疑惑了:既然每次都手动关闭了连接,为什么还会有大量CLOSE_WAIT状态的连接存在呢?
如果问题不在业务调用代码上,那么只能是这个业务接口具有的某种特殊性导致了问题的发生;通过抓包分析发现该接口有一个明显特征:接口返回报文较大,平均在500KB左右。那么问题就极有可能是报文过大导致了某种异常,造成了连接不能被复用也不能被释放。
开始分析之前,我们需要了解一个基础知识:Http的长连接和短连接。所谓长连接就是建立起连接之后,可以复用连接多次进行数据传输;而短连接则是每次都需要重新建立连接再进行数据传输。
而通过对接口的抓包我们发现,响应头里有Connection:keep-live字样,那我们就可以重点从HttpClient对长连接的管理入手来进行代码分析。
2.6.1 连接池初始化
初始化方法:
进入PoolingHttpClientConnectionManager这个类,有一个重载构造方法里包含连接存活时间参数:
顺着继续向下查看:
manager的构造方法到此结束,我们不难发现validityDeadline会被赋值给expiry变量,那我们接下来就要看下HttpClient是在哪里使用expiry这个参数的;
通常情况下,实例对象被构建出来的时候会初始化一些策略参数,此时我们需要查看构建HttpClient实例的方法来寻找答案:
此方法包含一系列的初始化操作,包括构建连接池,给连接池设置最大连接数,指定重用策略和长连接策略等,这里我们还注意到,HttpClient创建了一个异步线程,去监听清理空闲连接。
当然,前提是你打开了自动清理空闲连接的配置,默认是关闭的。
接着我们就看到了HttpClient关闭空闲连接的具体实现,里面有我们想要看到的内容:
此时,我们可以得出第一个结论:可以在初始化连接池的时候,通过实现带参的PoolingHttpClientConnectionManager构造方法,修改validityDeadline的值,从而影响HttpClient对长连接的管理策略。
2.6.2 执行方法入口
先找到执行入口方法:
org.apache.http.impl.execchain.MainClientExec.execute,看到了keepalive相关代码实现:
我们来看下默认的策略:
由于中间的调用逻辑比较简单,就不在这里一一把调用的链路贴出来了,这边直接给结论:HttpClient对没有指定连接有效时间的长连接,有效期设置为永久(Long.MAX_VALUE)。
综合以上分析,我们可以得出最终结论:
HttpClient通过控制newExpiry和validityDeadline来实现对长连接的有效期的管理,而且对没有指定连接有效时间的长连接,有效期设置为永久。
至此我们可以大胆给出一个猜测:长连接的有效期是永久,而因为某种异常导致长连接没有被及时关闭,而永久存活了下来,不能被复用也不能被释放。(只是根据现象的猜测,虽然最后被证实并不完全正确,但确实提高了我们解决问题的效率)。
基于此,我们也可以通过改变这两个参数来实现对长连接的管理:
这样简单修改上线后,处于close_wait状态的连接数没有再持续增长,这个线上问题也算是得到了彻底的解决。
但此时相信大家也都存在一个疑问:作为被广泛使用的开源框架,HttpClient难道对长连接的管理这么粗糙吗?一个简单的异常调用就能导致整个调度机制彻底崩溃,而且不会自行恢复;
于是带着疑问,再一次详细查看了HttpClient的源码。
开始分析之前,先简单介绍下几个核心类:
最大连接数(maxTotal)
最大单路由连接数(maxPerRoute)
最大连接数,顾名思义,就是连接池允许创建的最大连接数量;
最大单路由连接数可以理解为同一个域名允许的最大连接数,且所有maxPerRoute的总和不能超过maxTotal。
以浏览器为例,浏览器对接了头条和一点,为了做到业务隔离,不相互影响,可以把maxTotal设置成500,而defaultMaxPerRoute设置成400,主要是因为头条的业务接口量远大于一点,defaultMaxPerRoute需要满足调用量较大的一方。
【connectionRequestTimout】:指从连接池获取连接的超时时间;
【connetionTimeout】:指客户端和服务器建立连接的超时时间,超时后会报
ConnectionTimeOutException异常;
【socketTimeout】:指客户端和服务器建立连接后,数据传输过程中数据包之间间隔的最大时间,超出后会抛出SocketTimeOutException。
一定要注意:这里的超时不是数据传输完成,而只是接收到两个数据包的间隔时间,这也是很多线上诡异问题发生的根本原因。
【free】:空闲连接的容器,连接还没有建立,理论上freeSize=maxTotal -leasedSize
- availableSize(其实HttpClient中并没有该容器,只是为了描述方便,特意引入的一个容器)。
【leased】:租赁连接的容器,连接创建后,会从free容器转移到leased容器;也可以直接从available容器租赁连接,租赁成功后连接被放在leased容器中,此种场景主要是连接的复用,也是连接池的一个很重要的能力。
【pending】:等待连接的容器,其实该容器只是在等待连接释放的时候用作阻塞线程,下文也不会再提到,感兴趣的可以参考具体实现代码,其与connectionRequestTimout相关。
【available】:可复用连接的容器,通常直接从leased容器转移过来,长连接的情况下完成通信后,会把连接放到available列表,一些对连接的管理和释放通常都是围绕该容器进行的。
注:由于存在maxTotal和maxPerRoute两个连接数限制,下文在提到这四种容器时,如果没有带前缀,都代表是总连接数,如果是r.xxxx则代表是路由连接里的某个容器大小。
maxTotal的组成
整个过程分析完,了解了httpclient如何管理连接,再回头来看我们遇到的那个问题就比较清晰了:
正常情况下,虽然建立了长连接,但是我们会在finally代码块里去手动关闭,此场景其实是触发了“连接的释放”中的步骤2,连接直接被关闭;所以正常情况下是没有问题的,长连接其实并没有发挥真正的作用;
那问题自然就只能出现在一些异常场景,导致了长连接没有被及时关闭,结合最初的分析,是服务端主动断开了连接,那大概率出现在一些超时导致连接断开的异常场景,我们再回到
org.apache.http.impl.execchain.MainClientExec这个类,发现这样几行代码:
connHolder.releaseConnection()对应“连接的释放”中提到的步骤1,此时连接只是被放入了available容器,并且有效期是永久;
return new HttpResponseProxy(response, null)返回的ConnectionHolder是null,结合IOUtils.closeQuietly(httpResponse)的具体实现,连接并没有及时关闭,而是永久的放在了available容器里,并且状态为CLOSE_WAIT,无法被复用;
根据 “连接的产生与管理”的步骤3的描述,在free容器为空的时候httpclient是能够主动释放available里的连接的,即使连接永久的放在了available容器里,理论上也不会造成连接永远无法释放;
然而再结合“连接的产生与管理”的步骤4,当free容器为空了以后,从连接池获取连接时需要等待available容器里的连接被释放掉,整个过程是单线程的,效率极低,势必会造成拥堵,最终导致大量等待获取连接超时报错,这也与我们线上看到的场景相吻合。
PoolingHttpClientConnectionManager(60, TimeUnit.SECONDS)构造函数,尤其是调用量较大的情况,防止发生不可预知的问题; 4. 可以通过设置evictIdleConnections(5, TimeUnit.SECONDS)定时清理空闲连接,尤其是http接口响应时间短,并发量大的情况下,及时清理空闲连接,避免从连接池获取连接的时候发现连接过期再去关闭连接,能在一定程度上提高接口性能。
HttpClient作为当前使用最广泛的基于Java语言的Http调用框架,在笔者看来其存在两点明显不足:
END
6月5日,一张券商降薪截图在社交媒体疯传。截图提到,当日上午,某中字头头部券商召开大会,除了MD外全员降薪,且降薪不只是降奖金,而是直接降底薪。按照职级不同,SA1降6K,SA3降8K,VP降8K—10K。据了解,降薪大概率整体属实,但具体幅度有所差异,且不同区域、不同业务条线目前掌握的降薪情况也不尽相同。
今日,蔚来 CEO 李斌在 2023 高通汽车技术与合作峰会上爆料,蔚来第二代技术平台的全系车型已标配第三代骁龙座舱平台。
Meta公司周一(5月22日)推出了一个开源AI语言模型——大规模多语言语音(Massively Multilingual Speech, MMS)模型,可以识别和产生1000多种语言的语音——比目前可用的模型增加了10倍。研究人员表示,他们的模型可以转换1000多种语言,但能识别4000多种语言。
歌手孙燕姿在更新动态中回应了近日引发争议的“顶流AI歌手孙燕姿”,笑称粉丝已经接受她是“冷门”歌手,而AI成为了目前的顶流。
5月31日晚,荣耀方面对澎湃新闻记者表示,上海荣耀智能科技开发有限公司是荣耀位于上海的研究所,是荣耀在中国的5个研究中心之一,重点方向在终端侧核心软件、图形算法、通信、拍照等方面研究开发工作。荣耀强调,坚持以用户为中心,开放创新,与全球合作伙伴一起为用户提供最佳产品解决方案。
据北京市市场监督管理局公示信息,5月24日,苹果电子产品商贸(北京)有限公司因发布虚假广告被北京市东城区市场监督管理局处以20万元的行政处罚。
据外媒5月24日消息,全球最大的个人电脑制造商联想表示,在2023年1-3月期间,该公司裁员了约5%,这是由于PC市场不景气导致的。
日前,有网络博主号称拍摄到了小米首款汽车MS11的高清视频。从视频中可以看出,新车依旧包裹大面积的伪装,据该博主称,他之所以确定这是小米汽车,是因为靠近观察之后,发现它的三角形大灯轮廓和其最初手绘的小米汽车假想图几乎一模一样。
超过 350 名从事人工智能工作的高管、研究人员和工程师签署了这份由非盈利组织人工智能安全中心发布的公开信,认为人工智能具备可能导致人类灭绝的风险,应当将其视为与流行病和核战争同等的社会风险。
日前,以押注“颠覆性创新”著称的ARK Invest创始人Cathie Wood在接受媒体采访时表示,软件提供商将是人工智能狂潮的下一个受益板块。英伟达每卖出1美元的硬件,软件供应商SaaS供应商就会产生8美元的收入。
据报道,阿里巴巴研究员吴翰清已于近期离职,钉钉显示其离职时间是5月19日。在阿里内部,研究员的职级为P10。据消息人士透露,吴翰清离职后,选择AI短视频赛道创业,已经close一轮融资。对于上述消息,截至发稿,阿里尚未回应。
阿里巴巴集团官微宣布,2023年六大业务集团总计需新招15000人,其中校招超过3000人。同时表示,“近日,关于淘宝天猫、阿里云、菜鸟、本地生活各个业务裁员谣言传得很厉害,但谣言就是谣言。我们的招聘正在紧锣密鼓的进行。”
“现今每一个存在的应用都将被AI 2.0重构,我觉得整个AI大模型带来的机遇和技术浪潮,会比过去Windows和安卓大10倍。”李开复表示。
苹果发布Vision Pro头显,正式宣布开启空间计算时代;苹果还发布新款MacBook Air,新款Mac Studio,并展示了iOS17、iPadOS 17、macOS Sonoma和watchOS10等新系统;Vision Pro头显售价3499美元,将于2024年初正式在美国市场发售;华尔街并不看好Vision Pro,苹果股价周一创历史新高后由涨转跌。
5月25日,长城汽车就比亚迪秦PLUS DM-i、宋PLUS DM-i采用常压油箱,涉嫌整车蒸发污染物排放不达标的问题进行举报。
近日,一个名为“贾跃亭”的抖音账号悄然出现,带有“FF创始人、合伙人、首席产品及用户生态官, LeEco 乐视创始人”等标签,IP 地址显示为美国。
5月29日消息,继上周远超预期的财报业绩预测引得股价和市值史诗级暴涨后,今日,英伟达(NVIDIA)创始人兼CEO黄仁勋穿着标志性的皮衣,意气风发地出现在台北电脑展COMPUTEX 2023上,在主题演讲期间先是现场给自家显卡带货,然后一连公布涉及加速计算和人工智能(AI)的多项进展。
近日,苹果位于天猫的Apple Store官方旗舰店挂出直播预告,表示将在5月31日晚19时开启官方直播,这也是苹果官方在电商平台的全球首次直播。
前京东集团副总裁、京东探索研究院副院长梅涛自今年初离职后,确认在 AI 领域创业,成立生成式 AI 公司 HiDream.ai。