• HttpClient异步调用引发的程序挂起问题排查及解决


          在搭建搭建分布式系统时,基础组件与框架的重要性不言而喻。但是如果组件出现bug,真的很要命。虽然我们通过各种单元测试,拼命找bug,但是总有一些问题被盲目自信蒙蔽了双眼,很多时候我们认为这段代码100%没有问题,但是我想说,没有100%没有问题的代码,只有你没想到的应用场景。下面就说一下最近技术组件出现的一次离奇的故障。

          开始之前,先看看这个服务的压力,大约每分钟3700左右的样子,折合成TPS也就不到100的样子。

    image

          问题现象是,当服务程序重启后,系统一直没有结束的任务,并且线程持续增长,直到线程数增长到最大。

      image

          通过Dump分析发现,所有的线程都被一个线程阻塞,阻塞线程18.

    image

          进一步分析18号线程的堆栈发现,SetDatabaseConfig方法创建了一个Task,一直等待没有返回。

        image

          OK,看到这里,立马看一下程序的代码吧。代码中一个web请求,并且没有超时时间。

    image

          分析了这点代码后,简单整理了一下整个逻辑的调用过程。大体如下:

    image

          说明一下,获取数据库连接时,有一个全局的程序锁,锁中的逻辑是通过HttpClient远程访问WebAPI获取连接信息,并缓存到本地,上面的源代码就是在请求配置数据。通过上图我们可以看出,在HttpClient返回前,所有线程池中执行的任务,如果与数据库访问有关,都会被挂起。并且此时RPC服务持续受到请求,收到请求很快会被RPC服务消费并创建线程后交给线程池。这样可以解释为什么TeldHost.exe的线程持续增长的原因了。

          分析到这里,还是毫无头绪,第一次调用加锁了,没有并发请求,按理说很快就可以返回了。百思不得其解,干脆写个模拟程序测测获取数据库连接的方法吧。

    image[29]

          通过模拟程序测得,10并发时1s左右所有调用都完成了,100并发时需要9秒多,1000并发时需要350s。尼玛,果然有问题。通过review里面的代码,极度怀疑是HttpClient搞的鬼。通过翻看HttpClient的源代码,我们会发现,后台是通过Task实现的异步请求。看到这里恍然大悟,典型的HttpClient发起请求后,创建的task ,但是task拿不到线程的执行权引起的线程资源竞争。因为来自前端的压力一直很大,一直没有空闲线程,HttpClient一直在处理阻塞等待状态。

          果断把HttpClient异步调用改为同步调用,通过多次压测验证,问题得到解决。下面是修复后的代码。

    image

  • 相关阅读:
    Chroot 特性 ?
    服务端处理 Watcher 实现 ?
    四种类型的数据节点 Znode ?
    Zookeeper 文件系统 ?
    ZooKeeper 面试题?
    Mapper 编写有哪几种方式?
    Mybatis 的一级、二级缓存?
    Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
    一对一、一对多的关联查询 ?
    Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
  • 原文地址:https://www.cnblogs.com/vveiliang/p/6292528.html
Copyright © 2020-2023  润新知