微服务循环依赖调用引发的血案

问题表现 初步分析 探寻原因 验证 eureka 服务器 服务 foo 服务 boo jmeter jstack 总结   问题表现 最近的迭代转测后遇到了一个比较有意思的问题。在测试环境整体运行还算平稳,但是过一段时间之后,就开始有接口超时了,日志中出现非常多的 “java.net.sockettimeoutexception: read timed out”。试了几次重启大法,每次都是只能坚持一会之后,再次出现 sockettimeoutexception。
注意 :在测试环境于遇到问题重启服务,并不是一个好的实践,因为重启可能会让不容易出现的问题现场被破坏。如果问题在测试环境不能再重新,却在发版后出现在生产环境的话,那不仅会造成生产运维事件,还要在巨大的压力下去解决问题。
基于 spring boot + mybatis plus + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/ 初步分析 顺着测试汇报的出现问题的场景,跟踪调用链上相关服务的日志,发现出现了微服务之间循依赖调用。大致情况可以抽象如下所示(图中所有调用都是 http 协议):
client 调用服务 foo.hello() foo.hello() 逻辑中会调用服务 boo.boo() boo.boo() 又调用回服务 foo 的另外一个方法 another() 当然真实的场景要比较这个复杂,调用链更长,不过最终形成了环形依赖调用。至于这个环形依赖为什么回导致超时,当时想了多种可能,比如数据库慢查询、数据库锁、分布式锁等等。但是整个调用链上都是查询请求,而且查询相关的数据量也非常小,不会有锁存在。发生问题的时候也没有与查询数据相关的数据库写请求。
鉴于这个环形依赖调用确实是这个迭代版本中引入的变更,以及虽然没有理清其中的因果关系原理,但是这个环性依赖调用还是很可疑的,而且是不必要的环形调用。就抱着将环形依赖调用去掉试试看的态度,做了修复。修复完后,sockettimeoutexception 不再出现了。问题解决了。
基于 spring cloud alibaba + gateway + nacos + rocketmq + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/yudao-cloud 视频教程:https://doc.iocoder.cn/video/ 探寻原因 问题虽然不再出现,但是凭运气解决的问题,通常有可能不是真的的解决。只有弄清楚背后的原理,我们才能真正的确认问题是不是这个原因导致的,这样的修复是不是真的把问题解决了。
通过假设环形调用就是导致调用超时的直接原因。我们看看能不能推出因果关系。通过把foo 服务容器画的更详细一点,如下图:
通过这个图示,我们可以发现,如果容器中接收请求的线程池如果都在等待服务boo.boo() 的响应,而 boo 又需要调用回服务 foo.another()。这个时候,如果所有的线程都处于这样的状态,我们就会发现服务 foo 容器中以及没有线程来处理 boo 的请求了。某种程度上来说就是死锁了。到这里,我们就可以很确定了,这个环形依赖调用就是导致出现调用超时的罪魁祸首。当 client 发起的请求速度大于这个环形调用链的处理速度的时候,慢慢的就会导致服务 foo 的所有线程都进入这种死锁状态。
验证 这里只列出关键的代码,具体的代码可以参考 gitee 工程:https://gitee.com/donghbcn/circulardependency
eureka 服务器 建个简单工程将eureka server启动起来。
服务 foo 创建 springboot 工程实现 foo 服务。foo 通过 feignclient 调用 boo 服务。设置缺省的容器 tomcat 的最大线程数为 16,tomcat 默认配置最大线程数 200,对于验证这个场景有点了大了,要看到效果需要等的时间有点长。
application.properties
spring.application.name=demo-fooserver.port=8000eureka.client.serviceurl.defaultzone=http://localhost:8080/eurekaserver.tomcat.threads.max=16package com.cd.demofoo;import org.springframework.beans.factory.annotation.autowired;import org.springframework.web.bind.annotation.requestmapping;import org.springframework.web.bind.annotation.restcontroller;@restcontrollerpublic class foocontroller {    @autowired    boofeignclient boofeignclient;    @requestmapping(/hello)    public string hello(){        long start = system.currenttimemillis();        system.out.println([ + thread.currentthread() +                ] foo:hello called, call boo:boo now);        boofeignclient.boo();        system.out.println([ + thread.currentthread() +                ] foo:hello called, call boo:boo, total cost: +                (system.currenttimemillis() - start));        return hello world;    }    @requestmapping(/another)    public string another(){        long start = system.currenttimemillis();        try {            //通过 slepp 模拟一个耗时调用            thread.sleep(100);        } catch (interruptedexception e) {            e.printstacktrace();        }        system.out.println(foo:another called, total cost: + (system.currenttimemillis() - start));        return another;    }} 服务 boo 创建 springboot 工程实现 boo 服务。boo 通过 feignclient 调用 foo 服务。
package com.cd.demoboo;import org.springframework.beans.factory.annotation.autowired;import org.springframework.web.bind.annotation.requestmapping;import org.springframework.web.bind.annotation.restcontroller;@restcontrollerpublic class boocontroller {    @autowired    foofeignclient foofeignclient;    @requestmapping(/boo)    public string boo(){        long start = system.currenttimemillis();        foofeignclient.another();        system.out.println(boo:boo called, call foo:another, total cost: +                        (system.currenttimemillis() - start));        return boo;    }} jmeter 采用 jmeter 来模拟并发 client 调用。配置了30 个 线程,无限循环。
很快服务 foo 日志就卡死了。过一会 boo 的日志开始出现 sockettimeoutexception,如下图:
jstack 通过 jstack 我们可以看到 foo 进程的所有线程都卡在 hello() 调用上了。
总结 微服务之间的环形依赖类似于类之间的循环依赖,当依赖关系形成了环,会造成比较严重的问题:


智能家居的各种无线技术对比
未来的医疗行业将趋向于智能化
助力安防芯时代,康盈半导体精彩亮相2023 CPSE深圳安博会
基于Cube飞控的垂直起降固定翼Lynx VTOL海平面测试最新进展
气体继电器误动作的原因_减少气体继电器误动作的措施
微服务循环依赖调用引发的血案
锂电池冷热冲击试验方法
美国总统特朗普表示5G是一场美国必须取胜的竞赛
【社区精选】发烧友论坛一周精选——乒乓球发球、冰箱门报警器...
小米6什么时候上市?小米6最新消息:小米6价格,小米6配置大爆,小米6:双摄+虹膜识别,还是熟悉的味道还
蓝牙耳机哪个音质好,音质最好的蓝牙耳机2020
电路板参数有哪些参数
TVS二极管特性曲线图_TVS二极管应用
射频识别技术特点
为什么每个通信产品都需要老化?老化的方式有哪些?
红外线非接触体温计的工作原理及用途
老年大学开启5G智慧校园
一个新的金融时代将要来临
真无线蓝牙耳机推荐:全网销量TOP15排行榜
从电动汽车续航痛点到安全挑战 SiC功率元器件的作用逐渐在显现