线上 RTT 过长,我用这一招解决了!

简介: 线上 RTT 过长,我用这一招解决了!

大家好,我是坤哥


今天给大家分享一个这两天排查成功的案例,相信对大家会有些帮助。


大多数人应该听过一道经典的面试题:请详细地说出从浏览器地址栏输入 url 到最终呈现出结果的过程,越详细越好,为什么面试官这么喜欢问这道题呢,因为这个题涉及的面非常广,知识点非常多,如果你能完全吃透,非常有助于排查一些疑难杂症,今天我要分享的这个 case 就是个典型,废话不多说,进入正题(欢迎大家关注公众号「码海」,共同进步,^_^)


问题描述



前端同学发现新开发的项目接口有 1/3 概率出现 RTT(请求往返时间)大于 3 s 的情况,以登录接口为例,Chrome 请求所花时间如下


网络异常,图片无法展示
|


正常的 RTT 在几十 ms 左右(登录接口在 100 ms 左右),所以 3s 这个时延肯定不正常,于是着手排查,由于每个接口都可能超过 3s,所以下文皆以登录接口分析为例,因为登录接口逻辑相对比较简单。


排查思路



1. 排除浏览器本身的问题


估计大家看到这种问题马上就断定是 server 的问题,立马开始着手排查 server 的问题,不急,我们要先把浏览器本身可能导致请求缓慢的问题给排除了,浏览器本身可能因为「请求最大并发数量限制」,「更高优先级的请求插队,低优先级的任务被延后」等原因导致请求缓慢


所以为了排除浏览器本身造成的网络请求过慢,我们最好找一个其它的浏览器比如 Safari 或者终端 curl 请求来再重试下这个请求,看下请求是否依然缓慢,这里我两个方法都试了,用 Safari 也重现了 RTT 大于 3s 的情况,并且我用 curl 在终端请求也发现了 RTT 大于 3s 的情况,如何使用 curl 请求呢,这里提醒一下,大家千万不要傻傻地去构建一个 curl 请求,浏览器的网络请求本身可以导出成 curl 的形式的,在「network」中点击网络请求,选中「Copy as cURL」,就可获取此请求的 curl 表示方式


网络异常,图片无法展示
|


获得 curl 请求后,对此请求稍加改造,加上如下 -o,-s, -w 选项


curl -o /dev/null -s -w "time_connect: %{time_connect}\ntime_starttransfer:
 %{time_starttransfer}\ntime_nslookup:%{time_namelookup}\ntime_total: %{time_total}\n" 
浏览器copy而来的请求
复制代码


画外音:这里对几个参数简要作下说明: -o /dev/null 屏蔽原有输出信息,-s  静默模式,不输出任何东西,-w %{http_code} 控制额外输出


请求后会出现类似以下的结果


time_connect: 0.045897
time_starttransfer: 3.064762
time_nslookup:0.004328
time_total: 3.064777
复制代码


前面几个参数就不多做说明了,一般我们只要关心最后一个 time_total 参数即可,表示请求花费的全部时间。


综上,经过 safari 与 终端测试发现登录接口都有超过 3s 的现象出现,就此可以排除客户端的问题,接下来就是我们的重头戏:服务端排查!


2. 服务端排查


我们的 server 端是一个 Spring MVC 服务,对于登录接口来说,它的逻辑如下


@RestController
@RequestMapping(path = "/api/auth", method = RequestMethod.POST)
@Slf4j
public class AuthController {
    @RequestMapping(params = "member.login")
    public ResultTO<TokenDTO> login(@RequestBody UserLoginDTO userLogin) {
        /** 这里写登录逻辑 */ 
        return ResultTO.responseSuccess(tokenDTO);
    }
}
复制代码


里面的逻辑其实非常简单,就是根据用户名输入的账号密码去 db 请求一下校验用户信息是否正确,正确则生成 jwt token 返回给前端,看起来没啥问题,当然为了确保这段逻辑确实没问题,我们可以用一些工具来帮助我们实时验证一下,这里推荐一款阿里开源的 Java 诊断工具:Arthas,采用命令行交互模式,提供了丰富的功能,是排查 jvm 相关问题的利器,简单列举一下它的功能:


  • 提供性能看板,包括线程、cpu、内存等信息,并且会定时的刷新。
  • 根据各种条件查看线程快照。比如找出cpu占用率最高的 n 个线程等
  • 输出jvm的各种信息,如 gc 算法、jdk 版本、ClassPath 等
  • 查看/设置sysprop和sysenv
  • 查看某个类的静态属性,也可以通过 ognl 语法执行一些语句
  • 查看已加载的类的详细信息,比如这个类从哪个 jar 包加载的。也可以查看类的方法的信息
  • dump 某个类的字节码到指定目录
  • 直接反编译指定的类
  • 查看类加载器的一些信息
  • 可以让jvm重新加载某个类
  • 监控方法的执行,同时可以获取到执行的入参、出参以及抛出的异常
  • 追踪方法执行的调用栈,以及各个方法的调用时间


这里我们要用到它的最后一项功能,实时查看各个方法的调用时间,整个使用 arthas 的步骤如下


1、 首先我们要先下载一下 arthas,如下


curl -O https://arthas.aliyun.com/arthas-boot.jar
复制代码


2、 启动 arthas,启动后会展示一个 java 进程列表,我们选中 Arthas 将要调试的 Spring MVC 进程,以下选中 2,然后即可进入 arthas 的交互式界面(以下 arthas 展示的其实不只这么多信息,过滤了不少描述性的信息,只保留对大家有用的核心信息)


[root@d-sts-sh-1-spring-mvc-service-0 buser]# /opt/java8/bin/java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.1
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 99 org.apache.flume.node.Application
  [2]: 2698 /opt/apps/business/spring-mvc-service.jar
2
[arthas@2698]$
复制代码


然后我们就可以用 arthas 的 trace 命令来追踪方法调用,什么是 trace 命令,官方对其功能的介绍如下:


追踪方法内部调用路径,并输出方法路径上的每个节点上耗时,trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。


现在我们要跟踪 login 的实时耗时,那就可以指定「trace + 此方法所在类的全限定名(包名+类名)+方法名」来跟踪了,如下


[arthas@2698]$ trace com.xxx.AuthController login
复制代码


然后在浏览器执行登录操作,当浏览器登录时间为 3s+ 时, trace 对应的追踪结果如下


网络异常,图片无法展示
|


可以看到登录只花费了 76 ms,远达不到 3 s,那是否可以就此断定此台机器服务没问题呢?PS:我们线上的 SpringMVC 服务部署了两台机器,另一台机器也打开了 arthas 调试,也是 76 ms 左右


答案是不行,我们先来看一下 Spring MVC 的请求流转图


网络异常,图片无法展示
|


上图黄色部分的处理器即处理业务逻辑的 Controller,可以看到从请求到 Controller 还要经过 filter chain,DispatcherService 等类,在最开始的 filter chain 中,紫色的 JwtAuthorizationFilter 是我们自定义的 filter,是否有可能是这个 filter 处理过慢导致的呢,所以我们最好在请求的起始点 OncePerRequestFilter 的 doFilter 方法(实际上在此 filter 之前还要经过不少流转,不过在此之前都是框架的正常处理流程,可忽略) 来执行 arthas 的 trace。


当浏览器的登录请求为 3s+ 时,再次观察此次的 trace 结果:


网络异常,图片无法展示
|


也不过区区 81 ms! 在生产的两台机器上都试了多次,结论为当前端请求为 3s 时,两台机器的执行时间都为 81 ms 左右!**至此可以断定线上的两台机器 SpringMVC 服务是没有问题的!**既然线上机器服务没有问题,那只能从流量的流转路径着手了,客户端发出请求要经过哪些流程才能到达 SpringMVC 服务?


网络异常,图片无法展示
|


可以看到请求需要经过反向代理层,接入层后才能到达我们的站点层(即我们的 Spring MVC 服务),也就是说从「反向代理层到接入层」及「接入层到站点层」都可能导致请求缓慢,于是我把我用 arthas trace 执行的结果(MVC 服务执行时间 80ms 左右)与前端请求有 1/3 的概率超过 3s 的结论告诉了运维,让他们排查一下从反向代理层到站点层这中间是否有啥问题,不一会儿果然查出了问题。


结论是这样的:本来 MVC 服务的机器有三台,后来缩容了一台,变成了两台,但接入层 kongfu 依然持有这台被缩容的机器 ip,没有将其踢掉,所以前端流量打进来后,由于接入层的负载均衡策略,请求是有 1/3 的概率打到这台下线机器的 ip 上的,由于这个 ip 对应的机器无法响应这个请求,等到超时后,kongfu 会重试把这个请求打到另外正常的两台机器中的任意一台,也就是说请求 3s 中的大部分时间花在了等待那台不正常的 ip 机器响应上了。


网络异常,图片无法展示
|


有人可能会问,机器被踢掉了,接入层 kongfu 应该是能检测其下线的吧,怎么还会给这台下线的机器发请求呢?


是的,kongfu 会通过端口检测来检测机器是否存活的,但问题是,这台被缩容的机器虽然被回收了,但它的 ip 也是可以重新被分配给其他机器的,这种情况下 kongfu 通过端口检测就会认为它持有的 ip 对应的机器是存活的,而这台被分配此 ip 的机器又刚好不是 Spring MVC 服务,那正常 MVC 请求打给它的话,它就无法处理了,只能等到请求超时再由 kongfu 重试转发给正常的机器。


有人可能会好奇,运维是怎么查出来的呢,通过 「curl -I www.example」的形式可以输出开头信息,然后加上 -b 选项可以带上 cookie,我们的接入层如果发现请求里带有某些特殊的 cookie 会返回一个名为「X-KF-Via」的头字段,如下


X-KF-Via: agw(bip=10.65.x.1:8001,10.65.x.2:8001;b=mvc_service)
复制代码


这个头字段表示,请求 mvc_service 服务总共请求了两台机器,第一台 10.65.x.1 未成功后,再接着重试 10.65.x.2:8001,所以由此可以排查出 10.65.x.1 这台机器有问题,所以你看熟悉系统架构有多重要,如果我早知道有这么一个选项,就可以一步到位排查出此问题了


知道了问题所在,处理方案就很简单了,直接把这台有问题的机器从 kongfu 摘掉就行了


总结



排查的思路其实相对比较清晰,但一定要对请求的整个流转流程有一个比较清醒的认识,这样才能快速判断出问题所在,现在再回头看下开头的那道经典面试题「请详细地说出从浏览器地址栏输入 url 到最终呈现出结果的过程,越详细越好」,相信你会颇有感触,这道面试题如果你对请求流转的每个点都吃透得话,将极大地提升你排查解决问题的能力,举个例子,之前就有人反馈这样的一个问题:


在做 Server 压力测试时发现,客户端给服务器不断发请求,并接受服务器端的响应。发现接收服务器响应的过程中,会出现 recv 服务器端响应,阻塞 40ms 的情况,但是查看 server 端日志,Server 都在 2ms 内将请求处理完成,并给客户端响应


如果你了解 TCP,就知道它是由于 TCP 的延迟确认机制和 Nagle 算法及拥塞控制导致的,自然而然就会朝着这个方向 去解决了,比如打开 TCP_NODELAY 选项等。

相关文章
|
4月前
|
人工智能 数据安全/隐私保护
你试过一秒钟出现在世界各地的感觉吗?
【8月更文挑战第13天】你试过一秒钟出现在世界各地的感觉吗?
|
4月前
|
缓存 算法 网络性能优化
解决网络延迟和阻塞,有它,不服都不行!
解决网络延迟和阻塞,有它,不服都不行!
|
前端开发 Cloud Native 大数据
坑爹,线上同步近 3w 个用户导致链路阻塞引入发的线上问题,你经历过吗?
坑爹,线上同步近 3w 个用户导致链路阻塞引入发的线上问题,你经历过吗?
将字符串转为时间晚了5天,避坑指南
将字符串转为时间晚了5天,避坑指南
65 1
|
SQL 运维 监控
一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
197 0
|
存储 API
好好住抓图记录
最近家里正在装修,实在是不知道怎么做,之前看好好住APP上有不少设计的图,部分还挺好看。。就去看了下有没有WEB端,结果还真有,就有了下文,我抓了几万张图片,然后根据关键字进行分类,从里面找心仪的设计。
好好住抓图记录
|
分布式计算 大数据 Java
关于学习的思考 - 不行就用笨办法【一千零一日】
做一个终身学习者,以不断提升认知为人生目标,向终身学习中致敬。
1402 0