这个案例是最近刚发生不久的,只是这个雷的历史实在是久远。php
公司在3月底由于一次腾讯云专线故障,整个支付系统在高峰期中止服务将近10分钟。并且当时为了快速解决问题止损,重启了支付服务,过后也就没有了现场。咱们支付组在技术架构上原先对专线故障的场景作了降级预案,但故障时预案并无生效,因此此次咱们须要排查清楚降级没有生效的缘由(没有现场的过后排查,挑战很是大)。html
首先回顾一下微信支付的流程(也能够参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4):apache
这个过程是同步的,若是咱们的支付系统由于网络问题,没有取到prepay_id,那么用户就没法支付;api
咱们的预案很是简单,就是在请求api.mch.weixin.qq.com时,在HTTPClient中设置了一个超时时间,当支付请求超时时,咱们就请求微信支付的另一个备用域名api2.mch.weixin.qq.com,咱们的超时时间设置的是3秒;tomcat
每次网络抖动的时候,咱们从监控中都能发现,咱们的超时时间并无彻底起做用。从故障后的监控看平均执行时间达到了10秒,超时时间(3秒)彻底无论用:从日志中进一步分析到,不少请求都是在10秒以上,甚至10分钟后才报超时异常。10分钟后再降级到备份域名显然已经没有什么意义了。这让咱们开发很不解,为何HttpClient的超时设置没有生效,难道是HttpClient的bug?服务器
之前咱们也怀疑过本身封装的HTTPClient组件有问题,可是咱们写了一个并发程序测试过,当时并无测试出有串行问题或者不支持并发的问题;微信
最近经过咱们测试(咱们组其中一个开发在测试环境对故障进行了复现)和调研后,咱们发现支付系统使用的封装后的HttpsClient工具,同一时间最多只容许发起两个微信支付请求;当这两个请求没有迅速返回的时候(也就是网络抖动的时候),后面新的请求,只能排队等候,进而block住线程耗尽tomcat的线程;超时未生效的缘由是由于CloseableHttpClient默认的实现对网络链接采用了链接池技术,当链接数达到最大链接数时,后续的请求只能排队等待链接,根本就没法取得发起网络请求的机会,因此也谈不上链接超时和响应超时;网络
系统原本应该这样:架构
实际倒是这样:并发
咱们从HttpClient的官方文档中证明了这一点,同时也写程序进行了验证(这其中的配置比较复杂和深刻,计划后续再写一篇文章进行说明,请持续关注汪汪队);
官方文档:http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html 2.3.3. Pooling connection manager
咱们访问微信支付域名api.mch.weixin.qq.com,不管咱们发起多少个请求, 在httpclient中就是对应一个route(一个host和port对应一个route),而每一个route默认最多只有两个connection;而这个Route的默认值,咱们代码中没有修改。因此,一台tomcat,实际上同一时间最多只会有两个请求发送到微信。网络抖动的时候,请求都会须要很长时间才能返回,由于咱们设置的是3秒响应超时,因此,当网络抖动时,咱们单台机器的qps就是3秒2个,极限状况下一分钟最多40个请求;更糟糕的状况,咱们的程序中微信退款的超时时间设置的是30秒,因此若是是退款请求,那就是1分钟只能处理4个请求,10台服务器一分钟也就只能处理40个请求;由于支付和退款都是共用的一个HttpClient链接池,因此退款和支付会互相影响;
按照HttpClient的设计,支付系统真实请求过程大概以下:
一、对于微信支付,缺乏压测。以前压测都是基于支付宝,而支付宝的调用模式和微信彻底不同,致使没法及时发现这个瓶颈;
二、研发对HttpClient等使用池技术的组件,原理了解不够深刻,没有修改默认策略,最终造成了瓶颈;
三、对报警细节观察不是很到位,每次网络抖动咱们只看到了网络方面的问题,却忽略了程序中超时参数未生效的细节,从而屡次错失发现程序缺陷的机会,因此“细节决定成败”;
知识点
一、HttpClient,Route
二、微信支付
三、池技术
更多案例请关注微信公众号猿界汪汪队