升级springboot导致的业务异步回调积压问题定位
1. 起因
A与B云侧模块特性联调的过程中,端侧发现云侧返回有延迟的情况。
7月19日与A模块一起抓包初步判断,B业务有积压的情况。
7月18日已经转侧B业务现网版本,由于使用一套逻辑。故可能存在请求积压的问题。(严重)
2. 定位过程
2.1 复现问题
15路压测大屏发现请求有将近十多秒的时延,对于B业务实时性要求极高的业务,这无疑是灾难性的。
由于B业务最近针对业务并没有修改关键代码,只对springboot等相关第三方包做了升级。对比升级前后日志发现一个线程的名称发生了变化
升级前:
升级后:
因为B业务的上传接口采用的是异步回调机制。
升级之前,当springmvc内部有异步回调的时候,每次都会创建线程去处理回调逻辑,即没有线程池的概念,每次都是创建新的线程。线程命名规则是:MvcAsync[xx]
[xx]标示数值,比如MvcAsync1,MvcAsync9,MvcAsync10...
升级之后发现线程名字变成了task-[xx], xx标示数值,似乎只是名称改了,且在长期运行日志中发现task-[xx]也是大体递增趋势。
2.2 找异常点
后来为了复现问题,我们采用了15路压测,利用Java VisualVM观察CPU,内存,进程等。CPU和内存都ok,但是发现了很奇怪的现象:在压测的时候,为什么task-[xx]不递增,而且只维持8个线程,而我们15路并发,自然是处理不了。
难道task-[xx]是线程池里面的线程?且业务明显很“忙”,为什么不继续创建线程?难道内部真的改成线程池,并且对线程数量做了最多8个线程的限制?
后来我做了单次调用的测试,发现,task-[xx]同一时刻最多有8个线程同时运行,如果一直运行的话,就会复用线程,与之前的每次都创建新的线程完全不同,而且只运行一次的线程的存活时间是大约60秒,这不是keepalive的特性吗?
通过分析可以基本判断异步回调已经不是每次都创建新的线程,而是可能内部有线程池?
这些都是分析和猜测,并没有直接的证据说明,而且我们是需要最终去修改问题,如果只是大体猜出问题在哪?但是却不知道如何修改,也是没有办法的。
2.3 确认问题及解决方案
没办法,只能撸代码。
对于springboot我们基本都是当黑盒使用的,代码确实看了大概,但是要完全靠走读代码很难确定问题(面向接口编程一般很难确定具体运行类),所以我们采用了单步调试。撸代码基本可以确定问题对应的代码范围,方便我们调试代码。
为了方便调试,我写了一个简单的demo(个人觉得这个很重要,最好的方式肯定直接利用业务代码调试,但是有的时候我们的业务很庞大或者调试条件比较苛刻)
@RequestMapping("/webAsyncTask")
public WebAsyncTask<String> webAsyncTask() {
System.out.println("外部线程:" + Thread.currentThread().getName());
WebAsyncTask<String> result = new WebAsyncTask<>(6000000L, new Callable<String>() {
@Override
public String call()
throws Exception {
System.out.println("内部线程:" + Thread.currentThread().getName());
return "web async task";
}
}); result.onTimeout(new Callable<String>() {
@Override
public String call()
throws Exception {
return "timeout";
}
}); result.onCompletion(new Runnable() { @Override
public void run() {
System.out.println("finish");
}
});
return result;
}
通过走读代码,确定在spring-web-x.x.x.RELEASE.jar包里面的类
org.springframework.web.context.request.async.WebAsyncManager
其中的AsyncTaskExecutor taskExecutort保存异步调度线程的创建方式。
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
默认赋值SimpleAsyncTaskExecutor,这个对象就是每次都创建新的线程去执行异步回调。
我们分别使用springboot-1.5.14.RELEASE和springboot-2.1.3.RELEASE调试代码,对于调试的断点也很重要,到底把断点加在什么地方呢?针对我们的这个问题,加在业务代码里面肯定是看不出问题。有个方法可以大体判断地方,通过线程调用堆栈,如下图(可放大,下同):
断点基本可以确定在WebAsyncManager.startCallProcessing()方法里面
springboot-1.5.14.RELEASE调试截图:taskExecutor就是SimpleAsyncTaskExecutor实例
springboot- 2.1.3.RELEASE调试截图:taskExecutor就是org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor实例,并且是一个线程池corePoolSize=8,keepalive=60sec,maxPoolSize= 2147483647,queueCapacity=2147483647
这些默认值肯定是不合理的。
补充:
(1)corePoolSize: 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
(针对该问题由于队列很大,任务一直在排队,导致新的线程创建不了,而且一直是8个)
(2)maximumPoolSize: 线程池中允许的最大线程数。如果阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize
(3)keepAliveTime: 线程空闲的存活时间,即当线程没有任务执行时,继续存活的时间,默认情况下,该参数只在线程数大于corePoolSize时才有用。
(4)workQueue: 必须是BlockingQueue阻塞队列,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。
至此才能确定问题是由于springboot升级底层改变了异步回调线程的管理方式。
解决方案,其实我在都读代码的时候已经发现了:即继承WebMvcConfigureationSupport重载方法configureAsyncSupport设置异步回调的线程池或还是采用SimpleAsyncTaskExecutor
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(threadPoolTaskExecutor());
}
3. 总结
线程池的设置一直都是很重要的问题,而且大多数框架提供的默认线程池针对具体业务基本都是不合理的,顺便吐槽一下,springboot对task-[xx]的命名,没有体现线程池,应该起一个更具功能和意义的名字,比如async-threadpool-2-thread-[xx]
升级springboot导致的业务异步回调积压问题定位的更多相关文章
- 无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护
golang/goroutine 和 swoole/coroutine 协程性能测试对比 - Go语言中文网 - Golang中文社区 https://studygolang.com/articles ...
- 深入浅出写一个多级异步回调从基础到Promise实现的Demo
今天一时兴起,写了一个渐进升级的异步调用demo,记录一下. 1. 最基础的同步调用 //需求:f2在f1之后执行,且依赖f1的返回值.如下: function f1(){ var s="1 ...
- Java按时间梯度实现异步回调接口
1. 背景 在业务处理完之后,需要调用其他系统的接口,将相应的处理结果通知给对方,若是同步请求,假如调用的系统出现异常或是宕机等事件,会导致自身业务受到影响,事务会一直阻塞,数据库连接不够用等异常现象 ...
- 支付回调地址 同步回调地址 异步回调地址 return_url和notify_url的区别
[微信支付]JSAPI支付开发者文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10 退款结果通知 ...
- Android异步回调中的UI同步性问题
Android程序编码过程中,回调无处不在.从最常见的Activity生命周期回调开始,到BroadcastReceiver.Service以及Sqlite等.Activity.BroadcastRe ...
- C“中断” 与 JS“异步回调” 横向对比
在底层C语言中,有一个非常重要而特别的概念,叫做“中断”.用比喻来说,我正在写着博客,突然我妈打个电话过来,我就离开了键盘去接电话了,然后写博客就中断了,我聊完电话回来再继续写.乍一听似乎并没有什么大 ...
- Java异步回调
作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 1.开始讲故事: 午饭的时候到了,可是天气太冷,根本不想出办公室的门,于是你拨通了某饭店的订餐电话“喂!你好 ...
- AFHTTPClient的异步回调模式
以前第一个版本,ios的http都用的同步模式,在很多地方会导致线程阻塞,自己开发了一个简易的AFHTTPClient的异步回调模式. 回调的protocol: @protocol MyAFNetwo ...
- jquery.Deferred promise解决异步回调
我们先来看一下编写AJAX编码经常遇到的几个问题: 1.由于AJAX是异步的,所有依赖AJAX返回结果的代码必需写在AJAX回调函数中.这就不可避免地形成了嵌套,ajax等异步操作越多,嵌套层次就会越 ...
随机推荐
- LifeGame
LifeGame 用例说明&用例图 用例名: 设置细胞颜色 说明 用户可以根据自己的喜好来设置细胞的颜色 主事件流 在菜单出点击需要的颜色游戏检测到菜单的返回的颜色更改细胞的颜色,最后显示出来 ...
- UCOSII消息队列
主结构体 typedef struct os_q { /* QUEUE CONTROL BLOCK */ struct os_q *OSQPtr; /* Link to next queue cont ...
- JSP中Get提交方式的中文乱码解决
最近对JSP&Servlert的原理很感兴趣,所以今天花时间看了一下:无奈在一个编码问题上困扰很久 这是我的解决思路: (1)检查网页(html/jsp)页面的编码: (2)检查服务器端的处理 ...
- SQL SERVER-Extendevent系统视图
--获得扩展事件的事件 select name,description from sys.dm_xe_objects where object_type='event' order by name - ...
- iptables和lvs
解压密码6Hai7Gf8 路由转发 0是不转发,1是转发 [root@m01 roles]# cat /proc/sys/net/ipv4/ip_forward 0 临时生效 [root@m01 ...
- QT生成的exe在其他电脑打开
首先说一下我的开发的平台:vs2017+QT5.9 我们首先先用release版本来编译一下程序,然后我们得到了一个exe程序但是这个程序是不能脱离你的平台,甚至是不能脱离你所在的文件夹,这是因为它需 ...
- python网络-动态Web服务器案例(30)
一.浏览器请求HTML页面的过程 了解了HTTP协议和HTML文档,其实就明白了一个Web应用的本质就是: 浏览器发送一个HTTP请求: 服务器收到请求,生成一个HTML文档: 服务器把HTML文档作 ...
- Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapt ...
- destoon二次开发-用户名、邮箱、手机账号中间字符串以*隐藏 扩展
因为dt里面有用户名.邮箱.手机账号等,所以想办法进行隐藏保护用户隐私,所以个人就试着写了这个代码. 在api/extend.func.php文件下增加以下代码: //用户名.邮箱.手机账号中间字符串 ...
- LG5325 【模板】Min_25筛
P5325 [模板]Min_25筛 题目背景 模板题,无背景. 题目描述 定义积性函数$f(x)$,且$f(p^k)=p^k(p^k-1)$($p$是一个质数),求 $$\sum_{i=1}^n f( ...