背景
需求是开发的一个功能接口,实现用户可以手动刷新数据库中部分表的业务数据,其中部分信息需要调用远程其他服务查询,由于业务场景下同步执行耗时较长,为了优化用户体验,所以该接口的业务逻辑处理部分采用异步的方式执行。 先看代码 起初调用接口的时候e
需求是开发的一个功能接口,实现用户可以手动刷新数据库中部分表的业务数据,其中部分信息需要调用远程其他服务查询,由于业务场景下同步执行耗时较长,为了优化用户体验,所以该接口的业务逻辑处理部分采用异步的方式执行。
问题
先看代码
@Resource
RemoteService remoteService;
@RequestMapping("/refresh")
public String refresh(@RequestBody RequestParam param){
log.info("start refresh");
// 参数校验
ThreadUtil.execAsync(()->{
exec(param);
});
return "ok";
}
public void exec(RequestParam param){
// 1.业务处理。。。。
// 2.远程查询
List<Info> query =
remoteService.list(param);
// 3.数据更新
// 4.记录日志
}
class RequestParam{
String msg;
}
起初调用接口的时候exec方法中第2步前(不包括)的日志都会正常打印,也无任何异常信息打印在控制台,但是无论入参是什么,第3、4步始终没有执行(数据库中没有新数据),其中第2步是Feign的远程调用
分析
起初怀疑是调用Feign远程接口出现了问题,毕竟Feign的远程调用是基于HTTP实现的,而HTTP又是不可靠的,所以怀疑是Fegin调用时出现网络波动导致请求丢失。但是Feign作为Springcloud一个Web客户端框架就算发生请求丢失等情况也应该会有对应的重试机制,不至于连异常信息都没有。

于是我注意到整个业务执行是发生在一个hutool工具类包中的一个异步API:**ThreadUtil.execAsync()**中执行的,但理论上来说异步执行无非就是另起一个java线程去执行业务代码,也会打印相对于的异常信息。因为当时时间紧任务重,为了短并快的解决问题就先把这段异步执行的代码改成了同步执行,再次调用该接口发现了醒目的异常堆栈信息(这里我开始怀疑是hutool的异步调用工具类把异常信息给吞掉了)
RuntimeException: [400] during [POST] to [http://creeks-sales-service:8080/goods/snapshot/listMap?goodsIds=5885897&goodsIds=5572005&goodsIds=5571736&goodsIds=5884651&goodsIds=5571849&goodsIds=5572218&goodsIds=5572224&goodsIds=5572047&goodsIds=5884879&goodsIds=5572217&goodsIds=5572003&goodsIds=5884821&goodsIds=5884576&goodsIds=5260313&goodsIds=5884650&goodsIds=5262034&goodsIds=5572045&goodsIds=5884650&goodsIds=5884426&goodsIds=5572152&goodsIds=5884661&goodsIds=5884727&goodsIds=5572024&goodsIds=5884564&goodsIds=5884639&goodsIds=5884641&goodsIds=5884696&goodsIds=5260321&goodsIds=5571853&goodsIds=5884775&goodsIds=5572223&goodsIds=5572324&goodsIds=5259718&goodsIds=5572224&goodsIds=5884679&goodsIds=5884494&goodsIds=5572227&goodsIds=5572055&goodsIds=5572372&goodsIds=5572384&goodsIds=5885925&goodsIds=5572325&goodsIds=5884634&goodsIds=5572217&goodsIds=5572325&goodsIds=5885531&goodsIds=5885536&goodsIds=5885552&goodsIds=5572130&goodsIds=5884624&goodsIds=5571736&goodsIds=5572085&goodsIds=5884721&goodsIds=5885897&goodsIds=5572217&goodsIds=5571837&goodsIds=5572152&goodsIds=5884695&goodsIds=5572324&goodsIds=5572393&goodsIds=5884597&goodsIds=5884609&goodsIds=5885924&goodsIds=5571815&goodsIds=5885072&goodsIds=5572008&goodsIds=5572223&goodsIds=5260316&goodsIds=5885892.....(后面还有好多)]
[GoodsSnapshotRemoteService#listMap(List)]: [<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;... (435 bytes)]
at feign.FeignException.clientErrorStatus(FeignException.java:195)
at feign.FeignException.errorStatus(FeignException.java:177)
at feign.FeignException.errorStatus(FeignException.java:169)
at com.codo.tech.creeksonapi.base.feign.FeignServerErrorDecoder.decode(FeignServerErrorDecoder.java:42)
at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy228.listMap(Unknown Source)
at com.codo.tech.creeks.statistics.server.business.impl.PerformanceDailyBusinessServiceImpl.refreshOrderMatchAuto(PerformanceDailyBusinessServiceImpl.java:296)
at com.codo.tech.creeks.statistics.server.business.impl.PerformanceDailyBusinessServiceImpl.refresh(PerformanceDailyBusinessServiceImpl.java:635)
at com.codo.tech.creeks.statistics.server.business.impl.PerformanceDailyBusinessServiceImpl$$FastClassBySpringCGLIB$$3af81143.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
at com.codo.tech.creeksonapi.base.log.ExecuteLogFormatter.logAround(ExecuteLogFormatter.java:187)
at com.codo.tech.creeksonapi.base.log.ExecuteLogFormatter.logAround(ExecuteLogFormatter.java:164)
at com.codo.tech.creeks.statistics.server.config.aop.LoggingAspect.logExecuteTime(LoggingAspect.java:34)
at sun.reflect.GeneratedMethodAccessor136.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
at com.codo.tech.creeks.statistics.server.business.impl.PerformanceDailyBusinessServiceImpl$$EnhancerBySpringCGLIB$$52cc029.refresh(<generated>)
at com.codo.tech.creeks.statistics.server.controller.remoteapi.report.DailyPerformanceController.refresh(DailyPerformanceController.java:68)
at com.codo.tech.creeks.statistics.server.controller.remoteapi.report.DailyPerformanceController$$FastClassBySpringCGLIB$$bdf1ac6a.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
at com.codo.tech.creeksonapi.base.log.ExecuteLogFormatter.logAround(ExecuteLogFormatter.java:187)
at com.codo.tech.creeksonapi.base.log.ExecuteLogFormatter.logAround(ExecuteLogFormatter.java:164)
at com.codo.tech.creeks.statistics.server.config.aop.LoggingAspect.logExecuteTime(LoggingAspect.java:34)
at sun.reflect.GeneratedMethodAccessor136.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
at com.codo.tech.creeks.statistics.server.controller.remoteapi.report.DailyPerformanceController$$EnhancerBySpringCGLIB$$da2441fe.refresh(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at brave.servlet.TracingFilter.doFilter(TracingFilter.java:68)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at brave.servlet.TracingFilter.doFilter(TracingFilter.java:87)
at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:139)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:747)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
报错原因显而易见,原来是我CV的时候把**@RequestBody粘成了@RequestParam 。。。。。。**

****服务使用之间如果使用 feign 相互调用的话,无论是 POST 或 GET 请求,如果拼接请求参数过多导致URL过长的话,会导致丢失部分数据或者报错。解决方法很简单
- 可以加大服务提供者的限制:
feign:
httpclient:
enabled: true
hystrix:
#设置feign开启hystrix(服务保护)
enabled: true
compression:
request:
# 开启数据压缩请求
enabled: true
# 压缩数据类型
mime-types: text/xml, application/xml, application/json
# 数据压缩下限 2048表示传输数据大于2048 才会进行数据压缩(最小压缩值标准)
min-request-size: 2048
# 开启数据压缩响应
response:
enabled: true
server:
compression:
enabled: true
mime-types: text/xml, application/xml, application/json
- 改用请求体传递请求数据,并在客户端从业务代码上限制请求的数据量(拆分成多次调用,避免一次携带过多请求数据)
:::info
个人建议不要修改请求参数的限制,遵循RESTful API设计规范!
:::
将采用第二种方案修改代码后接口就能正常调用了,但是为什么采用hutool的异步工具类会把异常信息给吞掉呢,于是我分析了一下这个异步执行API的源码
/**
* 执行有返回值的异步方法<br>
* Future代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞
*
* @param runnable 可运行对象
* @return {@link Future}
* @since 3.0.5
*/
public static Future<?> execAsync(Runnable runnable) {
return GlobalThreadPool.submit(runnable);
}
Hutool使用GlobalThreadPool持有一个全局的线程池,默认所有异步方法在这个线程池中执行。在这个API的相关实现里面没有发现任何有关于异常处理的代码,线程池的参数配置也很常规,最后我注意到这个API接受的参数竟然是有返回值,才发现原来这个API调用的是线程池的submit方法

使用subimt执行异步任务但不调用其get()方法,如果执行代码发生异常,这个异常则不会在堆栈信息中直接打印出来。这是因为submit提交的的任务是由ExecutorService来执行的,并且ExecutorService会捕获任务抛出的异常,然后封装在返回的Future对象中。
execute和submit的区别
- 返回值类型:execute方法没有返回值,而submit方法返回一个表示任务结果的Future对象。
- 可接受的任务类型:execute方法只接受Runnable任务,而submit方法可以接受Runnable任务和Callable任务。
- 异常处理:在执行任务时,execute方法将任务的异常抛出到调用者的线程中,而submit方法将异常封装在Future对象中,调用者可以通过Future对象获取异常信息。
总结
- CV的时候要小心,发布时Review代码
- 调用三方API的浅看一下人家的注释和参数
ps:如果头铁还是想用Hutool的异步工具类实现该业务,可以用**ThreadUtil.execute(Runnable runnable)**实现异步操作