干工第一天,这个api超时优化把我干趴下了!
近日我司进行云服务商更换,恰逢由我负责新上线的三方调用 api 维护管理,在将服务由阿里云部署到腾讯云过程中,我们压测发现在腾讯云调用京东接口时 TP999 抖动十分剧烈,尽管业务层有重试操作但是超时依然较多,并不满足业务要求…… 接下来针对过程中发现的种种问题我们便踏上了优化之路。
开端
那还是普通的一夜,突然群里蹦出一些报警消息,多条业务线调用外部 api 超时,dubbo 线程池数量不足,还好很快服务恢复了正常,不过这也为我提了个大醒,我维护的服务作为业务调用的聚合出口,一旦服务发生异常将会导致很多业务方失败。尽管在压测过程中我们便知道了迁移云后的 api 调用可用性会有明显下降,但在 C 端业务还未迁移时便出了这么大影响确实是前期准备不足。
基于表面问题分析
出现问题先从最容易下手的地方开始解决,先分析问题,线程池飙高报警,结合日志、APM(skywalking) 工具进行排查,发现报警时刻线程数量和 cpu 均出现飙升情况,由于服务主要提供的功能就是三方调用,很多线程卡在调用三方 api 等待响应结果步骤上,并且有响应结果的调用消耗在 http 的时间也特别长,这就很容易理解了,是由于三方 api 抖动加之服务创建 http 连接过多先是导致应用响应缓慢,再之服务超时触发重试瞬间导致应用负载翻倍,进而加剧了服务缓慢问题。在不具备限流降级能力时服务一旦出现这种情况很容易导致雪崩,万幸没有太多业务损失,作为一个成熟的程序员总不能给自己留坑啊,赶紧来填坑!
多方位填坑
问题找到了,方案自然不缺了
- 修改 http 调用方式,引入 http 连接池
这个方案其实在之前压测过程中便考虑采用了,由于使用的三方 sdk 在 http 调用中均使用 java 原生的 HttpURLConnection ,这在接口高并发情况下不停创建连接,性能实在太差了。考虑团队内大家熟悉程度直接用 HttpClinet 覆盖三方 SDK 中的 HttpUtil 实现,稍加修改即可。修改好重新进行压测,TP999 指标、cpu 使用率均有明显下降,这波漂亮,投入产出 ++!
- 第一个方案就万事大吉了吗?支持请求超时分级,线程隔离
在第一个方案开发上线后我们又发现了新问题,业务侧超时情况依然明显!这个原因很容易想到,针对不同业务方我们只配置了一份超时配置,尽管部分业务方(主要是 C 端业务,对接口超时十分敏感)会提前超时然后触发重试,但是我的应用线程依然在阻塞请求,这是资源的浪费啊,仔细想想,之前的问题还是没有解决,在客户端不停重试下服务的 dubbo 线程岂不是还得爆了啊。再优化,这次我想到了请求分级,由于不同业务方对接口的响应时效要求不同,我们进行了连接池隔离,针对不同的业务方调用可以在调用时设置请求分级,我们大致分类下创建了三种分级 ‘FAST’,'STANDARD','SLOW',不同分级由服务提供者去配置使用不同超时方案。服务稳定性又进一步,问题再一次得到了解决。
这里还要提醒下大家 HttpClinet 使用过程中一般会配置连接池使用,切记搭配连接池使用时超时参数要配置三个,分别为
ConnectionRequestTimeout 获取连接池连接的超时时间(这个大家最容易忽略了)
ConnectTimeout 连接超时时间
SocketTimeout socket 读取超时时间
合理应用重试(消费者重试)
尽管在提供者服务上已进行代码优化,但是为了提升业务成功率,合理的使用重试也是很有必要的。此处要注意如果服务响应较慢千万避免消费者的多级重试,如果我们的整个业务调用链每一层都做了重试那么就会导致链路中响应慢的服务压力愈发增长,严重的引发重试风暴,直接压垮服务,所以合理设置重试也是很关键的一环,这里我们后续也要考虑引入熔断降级方案,避免意外发生。
在上述三招连环出击后,问题基本告一段落,2C4G 200 dubbo 线程,30 http 线程,吞吐量固定 1k,服务的 TP999 在压测过程中基本可以稳定在 500 毫秒以下,满足各方要求。
问题再起
伴随着应用接入量越来越大,我们的 C 端业务方又给提出了一个新问题,服务的超时情况怎么和阿里的服务差那么大啊(此处仅是指京东接口),由于是新业务直接上线到腾讯云,怀疑可能是我们服务的性能问题,这个在之前只有一些 B 端业务我确实没有过多注意,必须把这个问题好好查一查,还是从 APM 工具侧查看,果然,接口的 TP999 抖动十分剧烈,很多达到超时阈值,而 C 端业务十分重视这些指标,这也值得我们去学习,往往这些指标上线很容易暴露出根本问题,在高并发下看 TP90、TP99 时指标看起来还是挺正常的,那是因为超高请求量将响应时间给平均下去了。
纠结求解
这次的问题不再简单,面对这些 TP999 的超时响应,我们在观测了应用的整体流量、JVM 情况等等,愣是没有找到瓶颈,有之前阿里云的应用对比我们坚信问题是存在的,可能是在网络请求层面。接下来我们一步步出击。
0、查看 APM 工具发现部分 HTTP 请求确实超时,还有一部分是 HTTP 请求耗时不算长,但是从 APM 统计的调用链路来看耗费在提供者服务的时间比较长导致超时了。上述为两个问题,分别来看,请求可以确定是超时的我怀疑是不是由于使用了 http 连接池,池中的链接是不是超时了,从 httpclient 的日志中我分析了一下并没有这个问题,因为三方响应的 header 只返回了 keepalived,并没有返回其有超时时间。第二个问题我反复查找当时并没有找到原因,服务压力不大,也没有发生长耗时的 GC,实在不好解释这个问题,但是后来问题找到了,继续看结果在下文。
1、为了观察应用的请求响应信息,我们对 http 出口进行了抓包,通过对大量请求的抓包分析,我们找到了在响应比较高的时候抓包的 ip 中竟然有香港的 ip,为了验证这个问题,我们去百度了 dns 解析,发现该 ip 确实为该域名所有。
2、我们的服务器和出口 ip 都是北京的为什么 dns 解析出来的 ip 会返回香港的呢?带着问题我们求助了腾讯云官方,在官方问题排查过程中,我们又做了其他测试,在服务器上写了一个 python 小脚本定时进行指定域名的 dns 域名解析,果然在跑了一段时间后出现了几次解析结果为香港的 ip,狂喜。此时腾讯云官方也有了回应,由于京东自己搭建的 DNS 解析服务器,但是无法准确识别腾讯云的 ip 地域,导致偶尔会解析到香港,解决方案是他们推动京东去识别腾讯云的 ip。
3、等官方解决进度迟缓啊,我们能有什么临时的解决办法吗?纠结之时我们也接入了架构组为我们搭建的 kong 出口网关,有专门的 net 出口,更高的带宽,完善的 granfa 监控,结合监控请求抖时展现更明显了,于是我们想到了固定 host,更改 dns 方案,由于固定 host 风险太大了,我们采用了更改 dns 的方案尝试。原来机器上默认是腾讯云的 dns,我们分别更换为阿里的 dns 和 114 的 dns 进行了测试,测试结果是差距不大最终我们采用了阿里的 dns,在更换阿里的 dns 后效果明显,原来 TP999 的尖刺明显减少,观察 kong 日志也不存在香港的 ip 了,这里就有点诧异腾讯不是说和 dns 没关系吗,为什么换了别人家的 dns 情况就好了很多(这个问题的结果是明确的,但是原因目前还是停留在猜测阶段,猜测是 114 和阿里 dns 对此有优化,所以在该场景下优于腾讯 dns,若大家有方案可以验证此问题欢迎提出来哦)。
小插曲,kong 网关的 dns 解析器是写在 kong.conf 配置文件中的,如果未配置该项则会读取系统的 /etc/resolv.conf 文件,每次 kong 启动后会读取配置并缓存到内存中,如果启动 kong 后再修改系统的 nameserver 对 kong 是不管用的!还好我们之前有在专门的机器中测试 dns 更换效果,所以才敢肯定是 kong 配置问题,但是来回也折腾了不少时间才找到问题。
在这里我也想告诉大家,和别人协作,如果可以请一定准备好对比数据,避免他人因质疑而不配合,或者合作结果并不如预期时可以有个对照。
4、记得最开始有一个问题当时没找到原因吗,‘一部分 HTTP 请求耗时不算长,但是从 APM 统计的调用链路来看耗费在提供者服务的时间比较长导致超时了。’ 这个的问题最后还是 http 请求超时的原因,我对照了 kong 的日志和服务的 httpClient 日志,发现出现这种问题的请求有个共同点就是请求头很快接收到了,但是 body 迟迟未读取到然后触发超时了。skyWaling 中展示的 HTTP 耗时很短的原因是因为只要开始有响应就算请求结束了,然后为什么会有只返回 header 的情况呢?我 ping 了一下出现问题的 ip 是香港的,响应时间明显要比北京的慢,而且偶尔会丢包。这个问题解释通了。
终是踏平
经过又一轮的问题解决,测试加验证效果耗费了近两周时间,服务的可用性也提升了一个层次,算是一个小坑,不过这也让我们对 http 的整个请求流程做了很多梳理和二次认知,以上便是我在解决这个超时问题的整个解决路径和心路历程,感谢看我的叨叨叨,希望我们可以一起共同进步。
更多精品文章,欢迎大家扫码关注我的公众号「码海」
干工第一天,这个api超时优化把我干趴下了!的更多相关文章
- Web API幂等、超时优化
幂等 当涉及业务数据的变更,不是简单的数据查询时, 在调用方相同条件有效重复请求时,就需要保持业务系统数据之间的一致性,不管请求多少次都会返回相同的结果. 比如一个订单支付接口,第一次请求返回支付成功 ...
- 一个扩展搜索API的优化过程
概述 API 是一个服务的门面,就像衣装是人的形象一样. 优雅的 API 设计,能让业务方使用起来倍儿爽,提升开发效率,降低维护成本:糟糕的 API 设计,则让业务方遭心,陷入混沌. 本文将展示一个扩 ...
- 计算机就是用命换的行业,多干一年程序猿,寿命将减少2年,干20年的编程苦力,基本60岁之前你就要OVER了
if c++==python:(869710179) 2013-7-6 10:21:31 计算机本来就是用命换的行业 爱笑的眼睛(373213735) 2013-7-6 10:21:55 if c ...
- 移动端API接口优化的术和结果
最近一直在忙工作的事情,所以文章写得有些少. 有3-5篇文章都是写到一半然后被别的事情给打断了,所以,我得找个时间好好补补. 最近一直在关注移动端接口API的可用性问题,在移动时代这个做这个优化能产生 ...
- 第一节: dingo/API 最新版 V2.0 之安装讲解(连载)
我发现关于dingo/API V2.0的资料少之又少,应该也是发布时间不久的原因.下面,我就来给大家讲解(翻译)下官方的英文文档,如果有说的不对的地方,请指正.先附上,官网wiki地址https:// ...
- 淘宝客订单api处理优化
首选我们看看api定义: http://open.taobao.com/api.htm?docId=38078&docType=2&scopeId=14474 注意下span这个参数 ...
- PHP xdebug API接口优化揪出了getimagesize这个鬼
在API优化list中,公司客户系统的服务号客服有个获取聊天消息的接口getHistory请求时间很长,就去优化了下,记下过程. 一,配置环境,追踪使用Xdebug: 1.在https://xdebu ...
- MariaDB链接超时优化
查看mysql server超时时间: MariaDB [(none)]> use xspeeder; MariaDB [xspeeder]> show global variables ...
- 对应第一篇文章api的编写
router.get('/api/tags/search/:list/:key/:page', function(req, res) { if(_.isEmpty(req.params.key)) { ...
随机推荐
- Linux中ssh登陆慢的两种原因
useDNS配置导致登陆慢 如果ssh server的配置文件(通常是 /etc/ssh/sshd_config )中设置 useDNS yes ,可能会导致 ssh 登陆卡住几十秒.将该配置项设为 ...
- golang strings.Split函数
golang strings.Split函数 https://play.studygolang.com/ package main import ( "fmt" "str ...
- C# 使用vs2017 创建类 时 注意点
1.创建新类后,在其他类无法new 这个新创建的类 ,怎么回事? 原因很简单,创建类时不带修饰符,默认是被保护的类 上图为创建类后的默认代码 ,没有修饰符 ,在其他类中无法引入改类的命名空间,会显示找 ...
- window10 查看端口列表 - 查看占用的进程-销毁该进程
cmd进入指令框后 查看端口列表 netstat -ano 查看占用的进程 [6666是端口号对应的进程数] tasklist|findstr "6666" 销毁该进程 [6666 ...
- VictoriaMerics学习笔记(1):翻译官方广告
先看看VictoriaMetrics官网网站上是如何作(tree)宣(new)传(bee)的: 官方广告 0.(监控领域)最快解决方案 为高性能而设计 便于安装 支持单机和群集版本 1.更高效的存储空 ...
- golang中浮点型底层存储原理和decimal使用方法
var price float32 = 39.29 float64和float32类似,只是用于表示各部分的位数不同而已,其中:sign=1位,exponent=11位,fraction=52位,也就 ...
- IoC容器-Bean管理XML方式(注入外部bean)
注入属性-外部bean (1)创建两个类service类和dao类 (2)在service调用dao里面的方法 (3)在spring配置文件中进行配置
- Python:使用pyinstaller打包含有gettext locales语言环境的项目
问题 如何使用 pyinstaller 打包使用了 gettext 本地化的项目,最终只生成一个 exe 文件 起因 最近在用 pyhton 做一个图片处理的小工具,顺便接触了一下 gettext,用 ...
- python 定义函数关键字def 简单介绍
一 在类中定义的def # python中def 是用来干什么的? # 可以定义函数,就是定义一个功能. class People(): def __init__(self): print(&quo ...
- java代码实现调用短信接口,发送短信验证。
一.代码示例 package com.aaa.zxf.login; import org.apache.commons.httpclient.HttpClient; import org.apache ...