加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

一次简单的 HTTP 调用,为什么时延这么大?抓个包分析下

发布时间:2019-07-16 20:35:51 所属栏目:教程 来源:Java高级技术架构
导读:1.最近项目测试遇到个奇怪的现象,在测试环境通过 Apache HttpClient 调用后端的 HTTP 服务,平均耗时居然接近 39.2ms。可能你乍一看觉得这不是很正常吗,有什么好奇怪的?其实不然,我再来说下一些基本信息,该后端的 HTTP 服务并没有什么业务逻辑,只是将
副标题[/!--empirenews.page--]

一次简单的 HTTP 调用,为什么时延这么大?抓个包分析下

1.最近项目测试遇到个奇怪的现象,在测试环境通过 Apache HttpClient 调用后端的 HTTP 服务,平均耗时居然接近 39.2ms。可能你乍一看觉得这不是很正常吗,有什么好奇怪的?其实不然,我再来说下一些基本信息,该后端的 HTTP 服务并没有什么业务逻辑,只是将一段字符串转成大写然后返回,字符串长度也仅只有 100 字符,另外网络 ping 延时只有 1.9ms 左右。因此,理论上该调用耗时应该在 2-3ms 左右,但为什么平均耗时 39.2ms 呢?

一次简单的 HTTP 调用,为什么时延这么大?抓个包分析下

一次简单的 HTTP 调用,为什么时延这么大?抓个包分析下

由于工作原因,调用耗时的问题,对我来说,已经见怪不怪了,经常会帮业务解决内部 RPC 框架调用超时的相关问题,但是 HTTP 调用耗时第一次遇到。不过,排查问题的套路是一样的。主要方法论无外乎由外而内、至上而下等排查方法。我们先来看看外围的一些指标,看能否发现蛛丝马迹。

2. 外围指标

2.1 系统指标

主要看外围的一些系统指标(注意:调用与被调用的机器都要看)。例如负载、CPU。只需一个 top 命令就能一览无余。

因此,确认了下 CPU 和负载都很空闲。由于当时没有截图,这里就不放图了。

2.2 进程指标

Java 程序进程指标主要看 GC、线程堆栈情况(注意:调用与被调用的机器都要看)。

Young GC 都非常少,而且耗时也在 10ms 以内,因此没有长时间的 STW。

因为平均调用时间 39.2ms,比较大,如果耗时是代码导致,线程堆栈应该能发现点啥。看了之后一无所获,服务的相关线程堆栈主要表现是线程池的线程在等任务,这就意味着线程并不忙。

是不是感觉黔驴技穷了,接下来该怎么办呢?

3. 本地复现

如果本地(本地是 MAC 系统)能复现,对排查问题也是极好的。

因此在本地使用 Apache HttpClient 写了个简单 Test 程序,直接调用后端的 HTTP 服务,发现平均耗时在 55ms 左右。咦,怎么跟测试环境 39.2ms 的结果有点区别。主要是本地与测试环境的后端的 HTTP 服务机器跨地区了,ping 时延在 26ms 左右,所以延时增大了。不过本地确实也是存在问题的,因为ping 时延是 26ms,后端 HTTP 服务逻辑简单,几乎不耗时,因此本地调用平均耗时应该在 26ms 左右,为什么是 55ms?

是不是越来越迷惑,一头雾水,不知如何下手?

期间怀疑过 Apache HttpClient 是不是有什么地方使用的不对,因此使用 JDK 自带的 HttpURLConnection 写了简单的程序,做了测试,结果一样。

4. 诊断

4.1 定位

其实从外围的系统指标、进程指标,以及本地复现来看,大致能够断定不是程序上的原因。那 TCP 协议层面呢?

有网络编程经验的同学一定知道 TCP 什么参数会引起这个现象。对,你猜的没错,就是 TCP_NODELAY。

那调用方和被调用方哪边的程序没有设置呢?

调用方使用的是 Apache HttpClient ,tcpNoDelay 默认设置的就是 true。我们再来看看被调用方,也就是我们的后端 HTTP 服务,这个 HTTP 服务用的是 JDK自带的 HttpServer。

  1. HttpServer server = HttpServer.create(new InetSocketAddress(config.getPort()), BACKLOGS); 

居然没看到直接设置 tcpNoDelay 接口,翻了下源码。哦,原来在这里,在 ServerConfig 的类中有这段静态块,用来获取启动参数,默认 ServerConfig.noDelay 为 false。

  1. static 
  2.  {  
  3. AccessController.doPrivileged(newPrivilegedAction<Void>() {  
  4. public Void run() {  
  5. ServerConfig.idleInterval = Long.getLong("sun.net.httpserver.idleInterval", 30L) *1000L;  
  6. ServerConfig.clockTick = Integer.getInteger("sun.net.httpserver.clockTick", 10000);  
  7. ServerConfig.maxIdleConnections = Integer.getInteger("sun.net.httpserver. 
  8. maxIdleConnections", 200);  
  9. ServerConfig.drainAmount = Long.getLong("sun.net.httpserver.drainAmount", 65536L);  
  10. ServerConfig.maxReqHeaders = Integer.getInteger("sun.net.httpserver. 
  11. maxReqHeaders", 200);  
  12. ServerConfig.maxReqTime = Long.getLong("sun.net.httpserver.maxReqTime", -1L)  
  13. ServerConfig.maxRspTime = Long.getLong("sun.net.httpserver.maxRspTime", - 
  14. 1L);  
  15. ServerConfig.timerMillis = Long.getLong("sun.net.httpserver.timerMillis", 1000L);  
  16. ServerConfig.debug = Boolean.getBoolean("sun.net.httpserver.debug");  
  17. ServerConfig.noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay");  
  18. return null; 
  19.  } 
  20.  }); 

4.2 验证

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读