热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

springboot服务调用超时_SpringBoot异步请求和异步调用,一文搞定

一、SpringBoot中异步请求的使用1、异步请求与同步请求特点:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分

一、Spring Boot中异步请求的使用

1、异步请求与同步请求

5cf59e18bb1ae7d4b0fea968cad410ac.png

特点:

可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。

一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

2、异步请求的实现

方式一:Servlet方式实现异步请求

@RequestMapping(value = "/email/servletReq", method = GET)  public void servletReq (HttpServletRequest request, HttpServletResponse response) {      AsyncContext asyncContext = request.startAsync();      //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理      asyncContext.addListener(new AsyncListener() {          @Override          public void onTimeout(AsyncEvent event) throws IOException {              System.out.println("超时了...");              //做一些超时后的相关操作...          }          @Override          public void onStartAsync(AsyncEvent event) throws IOException {              System.out.println("线程开始");          }          @Override          public void onError(AsyncEvent event) throws IOException {              System.out.println("发生错误:"+event.getThrowable());          }          @Override          public void onComplete(AsyncEvent event) throws IOException {              System.out.println("执行完成");              //这里可以做一些清理资源的操作...          }      });      //设置超时时间      asyncContext.setTimeout(20000);      asyncContext.start(new Runnable() {          @Override          public void run() {              try {                  Thread.sleep(10000);                  System.out.println("内部线程:" + Thread.currentThread().getName());                  asyncContext.getResponse().setCharacterEncoding("utf-8");                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");                  asyncContext.getResponse().getWriter().println("这是异步的请求返回");              } catch (Exception e) {                  System.out.println("异常:"+e);              }              //异步请求完成通知              //此时整个请求才完成              asyncContext.complete();          }      });      //此时之类 request的线程连接已经释放了      System.out.println("主线程:" + Thread.currentThread().getName());  }

方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

@RequestMapping(value = "/email/callableReq", method = GET)  @ResponseBody  public Callable callableReq () {      System.out.println("外部线程:" + Thread.currentThread().getName());      return new Callable() {          @Override          public String call() throws Exception {              Thread.sleep(10000);              System.out.println("内部线程:" + Thread.currentThread().getName());              return "callable!";          }      };  }  @Configuration  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {  @Resource  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;  @Override  public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {      //处理 callable超时      configurer.setDefaultTimeout(60*1000);      configurer.setTaskExecutor(myThreadPoolTaskExecutor);      configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());  }  @Bean  public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {      return new TimeoutCallableProcessingInterceptor();  }}

方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理

@RequestMapping(value = "/email/webAsyncReq", method = GET)    @ResponseBody    public WebAsyncTask webAsyncReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        Callable result = () -> {            System.out.println("内部线程开始:" + Thread.currentThread().getName());            try {                TimeUnit.SECONDS.sleep(4);            } catch (Exception e) {                // TODO: handle exception            }            logger.info("副线程返回");            System.out.println("内部线程返回:" + Thread.currentThread().getName());            return "success";        };        WebAsyncTask wat = new WebAsyncTask(3000L, result);        wat.onTimeout(new Callable() {            @Override            public String call() throws Exception {                // TODO Auto-generated method stub                return "超时";            }        });        return wat;    }

方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

@RequestMapping(value = "/email/deferredResultReq", method = GET)    @ResponseBody    public DeferredResult deferredResultReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        //设置超时时间        DeferredResult result = new DeferredResult(60*1000L);        //处理超时事件 采用委托机制        result.onTimeout(new Runnable() {            @Override            public void run() {                System.out.println("DeferredResult超时");                result.setResult("超时了!");            }        });        result.onCompletion(new Runnable() {            @Override            public void run() {                //完成后                System.out.println("调用完成");            }        });        myThreadPoolTaskExecutor.execute(new Runnable() {            @Override            public void run() {                //处理业务逻辑                System.out.println("内部线程:" + Thread.currentThread().getName());                //返回结果                result.setResult("DeferredResult!!");            }        });       return result;    }

二、Spring Boot中异步调用的使用

1、介绍

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

2、使用方式(基于spring下)

需要在启动类加入@EnableAsync使异步调用@Async注解生效

在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池。

代码略。。。就俩标签,自己试一把就可以了

3、注意事项

在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。

4、什么情况下会导致@Async异步方法会失效?

调用同一个类下注有@Async异步方法:

在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决

调用的是静态(static )方法

调用(private)私有化方法

5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

@Controller@RequestMapping("/app")public class EmailController {    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下    @Autowired    private ApplicationContext applicationContext;    @RequestMapping(value = "/email/asyncCall", method = GET)    @ResponseBody    public Map asyncCall () {        Map resMap = new HashMap();        try{            //这样调用同类下的异步方法是不起作用的            //this.testAsyncTask();            //通过上下文获取自己的代理对象调用异步方法            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);            emailController.testAsyncTask();            resMap.put("code",200);        }catch (Exception e) {            resMap.put("code",400);            logger.error("error!",e);        }        return resMap;    }    //注意一定是public,且是非static方法    @Async    public void testAsyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }}

开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。代码实现,如下:

@Service@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)public class EmailService {    @Autowired    private ApplicationContext applicationContext;    @Async    public void testSyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }    public void asyncCallTwo() throws InterruptedException {        //this.testSyncTask();//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);//        emailService.testSyncTask();        boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;        //以下才是重点!!!        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);        EmailService proxy = (EmailService) AopContext.currentProxy();        System.out.println(emailService == proxy ? true : false);        proxy.testSyncTask();        System.out.println("end!!!");    }}

三、异步请求与异步调用的区别

两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

四、总结

异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~



推荐阅读
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在List和Set集合中存储Object类型的数据元素 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • 本文详细探讨了Java事件处理机制的核心概念与实现原理,内容浅显易懂,适合初学者逐步掌握。通过具体的示例和详细的解释,读者可以深入了解Java事件模型的工作方式及其在实际开发中的应用。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 本课程深入探讨了 Python 中自定义序列类的实现方法,涵盖从基础概念到高级技巧的全面解析。通过实例演示,学员将掌握如何创建支持切片操作的自定义序列对象,并了解 `bisect` 模块在序列处理中的应用。适合希望提升 Python 编程技能的中高级开发者。 ... [详细]
  • 本文详细介绍了如何安全地手动卸载Exchange Server 2003,以确保系统的稳定性和数据的完整性。根据微软官方支持文档(https://support.microsoft.com/kb833396/zh-cn),在进行卸载操作前,需要特别注意备份重要数据,并遵循一系列严格的步骤,以避免对现有网络环境造成不利影响。此外,文章还提供了详细的故障排除指南,帮助管理员在遇到问题时能够迅速解决,确保整个卸载过程顺利进行。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 每日前端实战:148# 视频教程展示纯 CSS 实现按钮两侧滑入装饰元素的悬停效果
    通过点击页面右侧的“预览”按钮,您可以直接在当前页面查看效果,或点击链接进入全屏预览模式。该视频教程展示了如何使用纯 CSS 实现按钮两侧滑入装饰元素的悬停效果。视频内容具有互动性,观众可以实时调整代码并观察变化。访问以下链接体验完整效果:https://codepen.io/comehope/pen/yRyOZr。 ... [详细]
author-avatar
七里汀
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有