跳到正文
Kale的数字花园
目录 文章
/7 分钟/6 阅读/0 评论

背景

需求是开发的一个功能接口,实现用户可以手动刷新数据库中部分表的业务数据,其中部分信息需要调用远程其他服务查询,由于业务场景下同步执行耗时较长,为了优化用户体验,所以该接口的业务逻辑处理部分采用异步的方式执行。 先看代码 起初调用接口的时候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过长的话,会导致丢失部分数据或者报错。解决方法很简单

  1. 可以加大服务提供者的限制:
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
  1. 改用请求体传递请求数据,并在客户端从业务代码上限制请求的数据量(拆分成多次调用,避免一次携带过多请求数据)

:::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的区别

  1. 返回值类型:execute方法没有返回值,而submit方法返回一个表示任务结果的Future对象。
  2. 可接受的任务类型:execute方法只接受Runnable任务,而submit方法可以接受Runnable任务和Callable任务。
  3. 异常处理:在执行任务时,execute方法将任务的异常抛出到调用者的线程中,而submit方法将异常封装在Future对象中,调用者可以通过Future对象获取异常信息。

总结

  1. CV的时候要小心,发布时Review代码
  2. 调用三方API的浅看一下人家的注释和参数

ps:如果头铁还是想用Hutool的异步工具类实现该业务,可以用**ThreadUtil.execute(Runnable runnable)**实现异步操作

评论需要登录。生产环境使用 GitHub 登录,本地可前往 登录页 使用开发登录。

还没有评论,来抢沙发。