提高接口响应

news2024/11/23 23:43:12

1. 简介

接口响应慢会直接影响用户体验和降低业务效率。为了有效应对这一问题,合理使用多线程技术成为了一种高效的解决方案。通过将独立的任务分配给不同的线程进行并行处理,我们可以充分利用系统资源,避免单一任务阻塞整个系统,从而显著提高业务整体效率。先来看看如下的业务接口:

@GetMapping("/{id}")
public Map<String, Object> allInfo(@PathVariable("id") Long id) {
  // 获取积分信息
  Map<String, Object> score = this.restTemplate.getForObject("http://localhost:9001/api/scores/{id}", Map.class, id) ;
  // 获取订单信息
  Map<String, Object> order = this.restTemplate.getForObject("http://localhost:9001/api/orders/{id}", Map.class, id) ;
  // 获取交易信息
  Map<String, Object> trade = this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id) ;
  return Map.of("score", score, "order", order, "trade", trade) ;
}

上面代码中调用的3个接口如下:

// 积分接口
@GetMapping("/scores/{id}")
public Map<String, Object> index(@PathVariable("id") Long id) throws Exception {
  // 模拟耗时操作
  TimeUnit.SECONDS.sleep(1) ;
  return Map.of("data", String.format("获取用户【%d】积分成功", id)) ;
}
// 订单接口
@GetMapping("/orders/{id}")
public Map<String, Object> index(@PathVariable("id") Long id) throws Exception {
  // 模拟耗时操作
  TimeUnit.SECONDS.sleep(2) ;
  return Map.of("data", String.format("获取用户【%d】订单成功", id)) ;
}
// 交易接口
@GetMapping("/trades/{id}")
public Map<String, Object> index(@PathVariable("id") Long id) throws Exception {
  // 模拟耗时操作
  TimeUnit.SECONDS.sleep(2) ;
  return Map.of("data", String.format("获取用户【%d】交易成功", id)) ;
}

请求接口结果如下

接口查询成功,但是总耗时达到了5秒,这对于大多数用户来说是无法接受的。在现代的互联网应用中,用户对响应速度的要求越来越高,一个快速的响应可以大大提升用户体验和满意度。当接口查询耗时过长时,用户可能会面临等待时间过长、页面无响应等问题,这不仅影响了用户的正常使用,还可能导致用户流失。接下来通过异步的方式优化上面的代码。

2. 接口优化

上面代码优化如下

@GetMapping("/{id}")
public Map<String, Object> allInfo(@PathVariable("id") Long id) {
  Map<String, Object> result = new HashMap<>() ;
  // 获取积分信息
  CompletableFuture<Void> scoreTask = CompletableFuture.runAsync(() -> {
    System.out.printf("执行线程: %s%n", Thread.currentThread().getName()) ;
    Map<String, Object> score = this.restTemplate.getForObject("http://localhost:9001/api/scores/{id}", Map.class, id) ;
    result.put("score", score) ;
  }) ;
  // 获取订单信息
  CompletableFuture<Void> orderTask = CompletableFuture.runAsync(() -> {
    System.out.printf("执行线程: %s%n", Thread.currentThread().getName()) ;
    Map<String, Object> order = this.restTemplate.getForObject("http://localhost:9001/api/orders/{id}", Map.class, id) ;
    result.put("order", order) ;
  }) ;
  // 获取交易信息
  CompletableFuture<Void> tradeTask = CompletableFuture.runAsync(() -> {
    System.out.printf("执行线程: %s%n", Thread.currentThread().getName()) ;
    Map<String, Object> trade = this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id) ;
    result.put("trade", trade) ;
  }) ;
  scoreTask.join() ;
  orderTask.join() ;
  tradeTask.join() ;
  return result ;
}

查询结果

时间缩短到2s,控制台输出如下:

分别在不同线程中执行。

2.1 异步优化①

上面的代码还可以进行优化,有以下原因:

  1. 线程安全问题:多个线程修改共享变量result,result是在主线程中创建的,并且是在多个子线程中直接修改的,这可能会导致线程安全问题(虽然这里不会)。
  2. 返回值问题CompletableFuture<Void>类型并不适合这里,因为我们实际上需要获取结果。应该使用CompletableFuture<Map<String, Object>>来存储和返回每个请求的结果。
  3. 合并结果:我们应该在所有任务完成后合并结果,而不是直接修改主线程中的result对象。

根据上面的问题,代码优化如下:

@GetMapping("/{id}")
public Map<String, Object> allInfo(@PathVariable("id") Long id) throws Exception {
  // 使用CompletableFuture.supplyAsync来返回结果
  CompletableFuture<Map<String, Object>> scoreFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/scores/{id}", Map.class, id));


  CompletableFuture<Map<String, Object>> orderFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/orders/{id}", Map.class, id));


  CompletableFuture<Map<String, Object>> tradeFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id));


  // 使用CompletableFuture.allOf等待所有任务完成
  CompletableFuture.allOf(scoreFuture, orderFuture, tradeFuture).join();


  // 合并结果
  Map<String, Object> result = new HashMap<>();
  result.put("score", scoreFuture.get());
  result.put("order", orderFuture.get());
  result.put("trade", tradeFuture.get());


  return result;
}

这里代码其实还有问题,继续往下看。

2.2 异步优化②

上面代码中,如果有任何一个接口发生异常,那么将会导致该业务接口返回异常。如下示例

CompletableFuture<Map<String, Object>> tradeFuture = CompletableFuture
  .supplyAsync(() -> {
    System.out.println(1 / 0) ;
    return this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id) ;
  });

上面代码人为制造异常,最终接口调用如下结果:

为了优化这段代码,使其能够优雅地处理异常,并且当任何接口发生异常时不会影响到其他接口的调用结果,将代码做如下调整:

@GetMapping("/{id}")
public Map<String, Object> allInfo(@PathVariable("id") Long id) throws Exception {
  CompletableFuture<Map> scoreFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/scores/{id}", Map.class, id))
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  CompletableFuture<Map> orderFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/orders/{id}", Map.class, id))
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  CompletableFuture<Map> tradeFuture = CompletableFuture
      .supplyAsync(() -> {
        System.out.println(1 / 0) ;
        return this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id);
      })
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  // 使用CompletableFuture.allOf等待所有任务完成
  CompletableFuture.allOf(scoreFuture, orderFuture, tradeFuture).join() ;


  return Map.of("score", scoreFuture.get(), "order", orderFuture.get(), "trade", tradeFuture.get()) ;
}

请求结果如下

不会因某一个接口出现问题而影响到整个业务接口。

2.3 异步优化③

在上面的代码中通过join操作来获取最终执行的结果,它会阻塞当前主线程(Tomcat线程)直到所有任务完成。如果有很多这样的请求同时到达,它会直接影响tomcat整体的吞吐量,我们可以通过接口异步处理的方式来进异步的优化,代码调整如下:

@GetMapping("/{id}")
public Callable<Map> allInfo(@PathVariable("id") Long id) throws Exception {
  System.out.printf("请求开始: %d%n", System.currentTimeMillis()) ;
  CompletableFuture<Map> scoreFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/scores/{id}", Map.class, id))
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  CompletableFuture<Map> orderFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/orders/{id}", Map.class, id))
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  CompletableFuture<Map> tradeFuture = CompletableFuture
      .supplyAsync(() -> this.restTemplate.getForObject("http://localhost:9001/api/trades/{id}", Map.class, id))
      .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;


  Callable<Map> cb = () -> {
    CompletableFuture.allOf(scoreFuture, orderFuture, tradeFuture).join() ;
    return Map.of("score", scoreFuture.get(), "order", orderFuture.get(), "trade", tradeFuture.get()) ;
  } ;
  System.out.printf("请求结束: %d%n", System.currentTimeMillis()) ;
  return cb  ;
}

测试结果,控制台输出

通过输出结果看出,tomcat线程仅仅执行了3ms就返回。这样一来,tomcat整体的吞吐量将会明显的提高。

3. 注意事项

在上面的代码中CompletableFuture#supplyAsync方法调用默认情况下使用的是ForkJoinPool.commonPool()。在实际的生产环境我们应该指定自己的线程池。自定义线程池更好地控制并发级别、线程数、队列深度等参数,以确保系统资源的有效利用和避免资源耗尽。使用方法如下:

private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(18, 18, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000)) ;
@GetMapping("/{id}")
public Callable<Map> allInfo(@PathVariable("id") Long id) throws Exception {
  CompletableFuture<Map> scoreFuture = CompletableFuture
    .supplyAsync(() -> this.restTemplate.getForObject("http://xxx", Map.class, id), pool)
    .exceptionally(ex -> Map.of("data", String.format("接口发生异常: %s", ex.getMessage()))) ;
  // ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1869246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【十】【QT开发应用】QT中文乱码解决方案

QT中文乱码解决方案 粘贴代码导致的乱码 粘贴别人的代码时,在记事本里面"过一遍",然后再粘贴到QTCreator 使用u8 配置QT 不使用QT使用VS QT自选编码格式 结尾 最后&#xff0c;感谢您阅读我的文章&#xff0c;希望这些内容能够对您有所启发和帮助。如果您有任何问…

[渗透测试] 任意文件读取漏洞

任意文件读取漏洞 概述 漏洞成因 存在读取文件的功能&#xff08;Web应用开放了文件读取功能&#xff09;读取文件的路径客户端可控&#xff08;完全控制或者影响文件路径&#xff09;没有对文件路径进行校验或者校验不严格导致被绕过输出文件内容 漏洞危害 下载服务器中的…

首次线下联合亮相!灵途科技携手AEye、ATI亮相2024 EAC 易贸汽车产业大会

6月22日&#xff0c;2024 EAC 易贸汽车产业大会在苏州国际博览中心圆满落幕&#xff0c;泛自动驾驶领域光电感知专家灵途科技携手自适应高性能激光雷达解决方案全球领导者AEye公司&#xff08;NASDAQ:LIDR&#xff09;及光电器件规模化量产巨头Accelight Technologies&#xff…

ASM插桩——动态添加字段并生成get set 方法

1 首先创建一个实体类Student. 代码如下 package com.org.xcyz.asm;public class Student {private int id;private String name;private boolean sex;public int getId() {return id;}public void setId(int id) {this.id id;}public String getName() {return name;}public…

触摸屏与罗克韦尔AB PLC之间 ModbusTCP/IP无线以太网通讯实例

在实际系统中&#xff0c;同一个车间里分布多台PLC&#xff0c;通过触摸屏人机界面集中控制。通常所有设备距离在几十米到上百米不等。在有通讯需求的时候&#xff0c;如果布线的话&#xff0c;工程量较大耽误工期&#xff0c;这种情况下比较适合采用无线通信方式。本方案以组态…

阿里云oss存储

文章目录 准备阿里云的OSS控制台创建bucket获取AccessKey java使用oss导入依赖官网demo修改参数运行demo代码 封装工具类Oss下载如何保证指定时间段内可以访问私有权限的图片文件&#xff1f; 准备阿里云的OSS 控制台 访问阿里云官网&#xff0c;登录以后&#xff0c;右上角有…

Omniverse 下载 isaac sim过慢的解决办法

比如在上海地区&#xff0c;下载isaac只有 200kb/s&#xff0c;这8个G下载要很长时间 对于着急的小伙伴&#xff0c;可以直接去日志里拿下载链接&#xff0c;在Omniverse里点右上角小人&#xff0c;点开里面SETTINGS&#xff0c;如图 点击&#xff0c;LOGS LOCATION&#xff0c…

校企合作,为人才培养注入新动力

树莓集团在校企合作育人方面取得了显著成效&#xff0c;通过共建专业、定制课程、实习实训等多种方式&#xff0c;实现了教育资源的优化配置和高效利用&#xff0c;为高校和企业提供了更多的发展机会和合作空间。 1、共建专业与实验室&#xff1a;树莓集团与高校共同建设数字产…

U-boot相关基础知识

U-boot和Bootloader之间的关系 U-Boot是Bootloader的一种实现&#xff0c;它专门用于嵌入式系统&#xff0c;特别是那些基于ARM、MIPS等处理器的系统。U-Boot提供了丰富的硬件支持和功能&#xff0c;使得开发者能够轻松地初始化硬件、加载操作系统内核&#xff0c;并进行一些基…

反向代购是怎么火起来的?今后的发展趋势如何?

反向代购和反向海淘的兴起可以归因于多个因素&#xff0c;这些因素共同推动了海外消费者对中国商品的需求和购买热潮。以下是对其火起来的原因的详细分析&#xff1a; 海外华人华侨的需求增加&#xff1a; 随着中国国际移民群体的扩大&#xff0c;海外华人华侨数量不断增多。这…

GD32F303 使用PA8输出内部时钟频率

前面给小伙伴介绍过串口发送和接收异常可能的一些原因&#xff0c;其中就有说到时钟频率对于异步通讯的重要性。而我们通过程序去配置的时钟都是理论值&#xff0c;那如果想要获得内部一些时钟频率的实际值&#xff0c;需要怎样做呢&#xff1f;今天&#xff0c;我们以GD32F303…

小项目——MySQL集训(学生成绩录入)

ddl语句 -- 创建学生信息表 CREATE TABLE students (student_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 学生ID,name VARCHAR(50) NOT NULL COMMENT 学生姓名,gender ENUM(男, 女) NOT NULL COMMENT 性别,class VARCHAR(50) NOT NULL COMMENT 班级,registration_date DATE CO…

RabbitMQ实践——定制一致性Hash交换器的路由字段

大纲 Property法定制交换器绑定队列测试 Header法定制交换器绑定队列测试 代码工程参考资料 在《RabbitMQ实践——利用一致性Hash交换器做负载均衡》一文中&#xff0c;我们熟悉了一致性Hash交换器的使用方法。默认的&#xff0c;它使用Routing key来做Hash的判断源。但是有些时…

基于Python的数码产品销售平台

1 项目介绍 1.1 研究目的和意义 本研究旨在设计和实现一个基于Python的数码产品销售平台&#xff0c;其核心目的在于通过先进的技术手段&#xff0c;提升数码产品销售的效率和用户体验&#xff0c;进而推动数码产品市场的繁荣发展。通过利用Python这一强大且灵活的编程语言&a…

可的哥视频会议(Meeting): 开启智能云端会议新纪元!

随着远程办公和全球化协作需求的不断增长&#xff0c;企业亟需一种更高效、更便捷的会议解决方案。在这样的背景下&#xff0c;可的哥&#xff08;Codigger&#xff09;视频会议&#xff08;Meeting&#xff09;应运而生&#xff0c;为企业提供了全新的沟通与协作方式。 可的哥…

AUTOSAR以太网之IPv4

系列文章目录 返回总目录 文章目录 系列文章目录一、IPv4报文格式二、主要函数1.IPv4_Init()2.IPv4_Receive()3.IPv4_Transmit()一、IPv4报文格式 二、主要函数 1.IPv4_Init() 这个函数除了对模块配置进行初始化,如果有分包和组包使能,则会对一些相关配置进行初始化如buf长…

Codeforces Round 954 (Div. 3) A~F

A.X Axis&#xff08;暴力&#xff09; 题意&#xff1a; 在 X X X轴&#xff08; 1 ≤ x i ≤ 10 1\leq x_i\leq 10 1≤xi​≤10&#xff09;上有三个点&#xff0c;其整数坐标分别为 x 1 x_1 x1​、 x 2 x_2 x2​和 x 3 x_3 x3​。您可以选择 X X X轴上任何一个整数坐标为 …

免费恢复微信好友的聊天记录(已删除的好友不能恢复)

非常简单,适用于未删除的微信好友的聊天记录恢复,支持导出 1、下载楼月微信聊天记录导出恢复助手 - 导出手机微信聊天记录 2、官方原文教程链接&#xff1a;官方原文教程链接https://www.louyue.com/weixin.htm

高考填报志愿,要做到知己知彼兼顾平衡

寒窗苦读&#xff0c;无非就是希望能够考上一所理想的大学&#xff0c;不过自从高考改革以后&#xff0c;高考结束后只是第一阶段&#xff0c;接下来第二阶段应对高考填报志愿也同样重要。 如何选择合适的院校、专业&#xff0c;考生和家长都需要做好充足的准备&#xff0c;在收…

视频组合其他内容生成二维码的方法,多内容二维码的生成技巧

现在通过二维码来播放视频的使用场景越来越多&#xff0c;通过这种方式能够更加简单便捷的让用户获取内容&#xff0c;无需下载视频内容&#xff0c;有效提升用户的体验效果。那么在制作视频二维码时&#xff0c;怎么加入其他的内容呢&#xff0c;比如图片、文件、文本、音频等…