继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

使用Java的CompletableFuture提升API性能:一种实用的方法

莫回无
关注TA
已关注
手记 174
粉丝 3
获赞 2

Spring Boot API性能

Spring Boot | API 性能优化 | CompletableFuture

在当今快速变化的API开发世界中,性能是决定用户体验的关键因素。高延迟的API可能让用户感到烦躁,甚至导致收入流失。在这篇文章里,我会讨论使用Java的CompletableFuture来提升API性能的一个实用方案,并介绍了一个基于Spring Boot的概念验证(POC)。

背景:同步 API 的难题

当构建从数据库或外部服务等来源检索数据的微服务或API时,通常会遇到顺序的调用请求,这些调用请求会阻塞执行线程的运行,直到每个响应被返回。这样的同步方式会严重降低性能,尤其是在存在延迟(例如网络延迟或因Thread.sleep()引起的有意延迟)时。

为了展示这一点,我创建了一个简单的POC(概念验证),包含两个API接口来从后端获取产品详情,包括价格和库存信息。第一个API实现采用同步方式,意味着它依次等待每个操作完成。这将导致响应时间显著增加,特别是在引入人工延迟(如Thread.sleep())的情况下。

POC结构:两个API,两种方式

POC(点对点通信)由两个 API 组成,每个 API 负责检索产品详情,包括库存和价格。具体来说,这些 API 是按照以下结构实现的。

  • 数据仓库:管理数据访问,获取产品、价格和库存数据。
  • 服务:包含业务逻辑,协调从数据仓库中获取数据。
  • 门面:作为一层来暴露服务的功能,其中一个实现使用CompletableFuture异步处理,另一个则没有。
  • 控制器:提供API端点。
不使用 CompletableFuture 的同步实现

在第一个阶段中,代码逐个阻塞获取产品详情、库存量和价格。这里是一个简化的版本,

首先,从仓库获取产品详情。接着,获取价格信息和库存数据。然后,返回产品详情汇总,包含价格和库存信息。最后,完成上述步骤。

因为这些操作是顺序执行的,任何一个步骤的延迟(例如网络延迟或 Thread.sleep())都会阻塞后续的操作,从而使得 API 的响应时间变慢。

使用 CompletableFuture 的异步处理方式

在第二个立面中,我使用 CompletableFuture 实现了相同的 API 逻辑,进行异步非阻塞调用。每个步骤(获取产品、价格和库存信息)并行执行,从而减少了整体等待时间。

这里是一个简化了的CompletableFuture如何提升性能的例子:

  1. 异步执行:使用 CompletableFuture.supplyAsync(),我同时异步获取产品、价格和库存的数据。
  2. 非阻塞调用:当某个调用还在等待响应时,其他调用可以继续处理。
  3. 合并结果:当所有操作完成后,我使用 CompletableFuture.allOf() 将所有结果作为一个单一响应返回。
一个步骤接一个步骤的实施

下面是一步步的实施方法。

步骤一:数据库模式和领域模型

我们将使用一个基本的框架来存储产品、价格和库存信息。我们的应用程序会访问这些信息来获取数据。

以下SQL脚本用于创建商品数据库表和定义表间关系。

    -- 创建一个名为category的表,包含以下字段
    CREATE TABLE `category` (  
      `id` bigint PRIMARY KEY,  
      `name` varchar(255),  
      `type` varchar(255),  
      `created_at` timestamp,  
      `created_by` varchar(255),  
      `modified_at` timestamp,  
      `modified_by` varchar(255),  
      `status` varchar(255)  
    );  

    -- 创建一个名为products的表,包含以下字段
    CREATE TABLE `products` (  
      `id` bigint PRIMARY KEY,  
      `category_id` bigint,  
      `name` varchar(255),  
      `description` text,  
      `created_at` timestamp,  
      `created_by` varchar(255),  
      `modified_at` timestamp,  
      `modified_by` varchar(255),  
      `status` varchar(255)  
    );  

    -- 创建一个名为price的表,包含以下字段
    CREATE TABLE `price` (  
      `id` bigint PRIMARY KEY,  
      `product_id` bigint,  
      `price` double,  
      `valid_from` timestamp,  
      `valid_to` timestamp,  
      `created_at` timestamp,  
      `created_by` varchar(255),  
      `modified_at` timestamp,  
      `modified_by` varchar(255),  
      `status` varchar(255)  
    );  

    -- 创建一个名为inventory的表,包含以下字段
    CREATE TABLE `inventory` (  
      `id` bigint PRIMARY KEY,  
      `product_id` bigint,  
      `warehouse_id` bigint,  
      `available_quantity` integer,  
      `reserved_quantity` integer,  
      `created_at` timestamp,  
      `created_by` varchar(255),  
      `modified_at` timestamp,  
      `modified_by` varchar(255),  
      `status` varchar(255)  
    );  

    -- 在inventory表中添加product_id的外键约束,引用products表中的id
    ALTER TABLE `inventory` ADD FOREIGN KEY (`product_id`) REFERENCES `products` (`id`);  

    -- 在products表中添加category_id的外键约束,引用category表中的id
    ALTER TABLE `products` ADD FOREIGN KEY (`category_id`) REFERENCES `category` (`id`);  

    -- 在price表中添加product_id的外键约束,引用products表中的id
    ALTER TABLE `price` ADD FOREIGN KEY (`product_id`) REFERENCES `products` (`id`);
步骤 2: 存储库层
第二步:仓库模块

创建三个存储库来与数据库表进行交互。

    @Repository  
    public interface ProductRepository extends JpaRepository<Product, Long> {  
    }
    @Repository  
    public interface 价格Repository接口 extends JpaRepository<Price, Long> {  
        Optional<Price> findBy产品ID(Long 产品ID);  
    }
    @Repository  
    public interface 库存Repository extends JpaRepository<Inventory, Long> {  
        Optional<Inventory> findBy产品ID(Long 产品ID);  
    }

我们将要定义一个服务来协调处理对代码仓库的调用。

产品服务模块
public interface 产品服务接口 ProductService {  
    Optional<Product> findById(Long id);  
}  

@Service  
public class 产品服务实现类 ProductServiceImpl implements ProductService {  

    private static final Logger log = LoggerFactory.getLogger(ProductServiceImpl.class);  
    private final ProductRepository productRepository;  

    public ProductServiceImpl(ProductRepository productRepository) {  
        this.productRepository = productRepository;  
    }  

    @Override  
    public Optional<Product> findById(Long id) {  
        log.info("通过ID获取产品: {}", id);  
        addDelay();  
        return productRepository.findById(id);  
    }  

    private void addDelay() {  
        try {  
            Thread.sleep(2000);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
    }  
}
价格服务功能
    public interface 价格服务接口 {  
        Optional<价格> 根据产品ID获取价格(Long 产品ID);  
    }  

    @Service  
    public class 价格服务实现 implements 价格服务接口 {  

        private static final 日志记录器 日志 = LoggerFactory.getLogger(价格服务实现.class);  
        private final 价格存储库 价格仓库;  

        public 价格服务实现(价格存储库 价格仓库) {  
            this.价格仓库 = 价格仓库;  
        }  

        @Override  
        public Optional<价格> 根据产品ID获取价格(Long 产品ID) {  
            日志.info("正在查询产品ID {} 的价格", 产品ID);  
            添加延迟时间();  
            return 价格仓库.根据产品ID查找价格(产品ID);  
        }  

        private void 添加延迟时间() {  
            try {  
                Thread.sleep(2000);  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }  
    }
库存管理服务
    public interface InventoryService {  
        Optional<Inventory> getInventoryByProductId(Long productId);  
    }  

    @Service  
    public class InventoryServiceImpl implements InventoryService {  

        private static final Logger log = LoggerFactory.getLogger(InventoryServiceImpl.class);  
        private final InventoryRepository inventoryRepository;  

        public InventoryServiceImpl(InventoryRepository inventoryRepository) {  
            this.inventoryRepository = inventoryRepository;  
        }  

        @Override  
        public Optional<Inventory> getInventoryByProductId(Long productId) {  
            log.info("日志记录正在获取 productId {} 对应的库存信息", productId);  
            addDelay();  
            return inventoryRepository.findByProductId(productId);  
        }  

        private void addDelay() {  
            try {  
                // 延迟2秒  
                Thread.sleep(2000);  
            } catch (InterruptedException e) {  
                // 延迟2秒  
                throw new RuntimeException(e);  
            }  
        }  
    }
第四步:外观层(Facade Layer)

外观负责管理服务的业务逻辑。我们分别为同步和异步情况设置了单独的外观。

产品 — 同步功能
    @Component  
    public class ProductFacade {  

        private static final Logger log = LoggerFactory.getLogger(ProductFacade.class);  

        private final ProductService productService;  

        private final InventoryService inventoryService;  

        private final PriceService priceService;  

        public ProductFacade(ProductService productService, InventoryService inventoryService, PriceService priceService) {  
            this.productService = productService;  
            this.inventoryService = inventoryService;  
            this.priceService = priceService;  
        }  

        /**  

* 获取产品详细信息的门面,用于productId {}  

*  

* @param productId 输入的productId  

* @return ProductDetailDTO  
         */  
        public Optional<ProductDetailDTO> getProductDetail(Long productId) {  
            log.info("获取产品详细信息的门面,用于productId {}", productId);  
            Optional<Product> productOpt = productService.findById(productId);  
            if (productOpt.isEmpty()) {  
                return Optional.empty();  
            }  
            // 获取产品的价格信息  
            Optional<Price> priceOpt = priceService.getPriceByProductId(productId);  

            // 获取产品的库存信息  
            Optional<Inventory> inventoryOpt = inventoryService.getInventoryByProductId(productId);  

            // 如果产品存在,则将详情合并为产品详细信息  
            return productOpt.map(product -> {  
                Price price = priceOpt.orElse(null);  
                Inventory inventory = inventoryOpt.orElse(null);  
                assert inventory != null;  
                assert price != null;  
                return new ProductDetailDTO(productId, product.getCategory().getName(),  
                        product.getName(), product.getDescription(), inventory.getAvailableQuantity(),  
                        price.getPrice(), product.getStatus());  
            });  
        }  
    }
ProductFacade — 异步功能
@Component  
public class ProductAsyncFacade {  

    private static final Logger log = LoggerFactory.getLogger(ProductAsyncFacade.class);  

    private final ProductService productService;  

    private final InventoryService inventoryService;  

    private final PriceService priceService;  

    public ProductAsyncFacade(ProductService productService, InventoryService inventoryService, PriceService priceService) {  
        this.productService = productService;  
        this.inventoryService = inventoryService;  
        this.priceService = priceService;  
    }  

    // 通过 productId 获取商品详情的 Future  
    private CompletableFuture<Optional<Product>> getProductById(Long productId) {  
        return CompletableFuture.supplyAsync(() -> productService.findById(productId));  
    }  

    // 通过 productId 获取价格详情的 Future  
    public CompletableFuture<Optional<Price>> getPriceByProductId(Long productId) {  
        return CompletableFuture.supplyAsync(() -> priceService.getPriceByProductId(productId));  
    }  

    // 通过 productId 获取库存详情的 Future  
    public CompletableFuture<Optional<Inventory>> getInventoryByProductId(Long productId) {  
        return CompletableFuture.supplyAsync(() -> inventoryService.getInventoryByProductId(productId));  
    }  

    /**  

* 异步获取商品详情  

*  

* @param productId 输入的 productId  

* @return ProductDetailsDTO  
     */  
    public Optional<ProductDetailDTO> getProductDetailById(Long productId) {  
        // 异步获取所有数据  
        CompletableFuture<Optional<Product>> productFuture = getProductById(productId);  
        CompletableFuture<Optional<Price>> priceFuture = getPriceByProductId(productId);  
        CompletableFuture<Optional<Inventory>> inventoryFuture = getInventoryByProductId(productId);  

        // 等待所有 Future 完成后再继续执行  
        CompletableFuture.allOf(productFuture, priceFuture, inventoryFuture).join();  

        // 合并所有获取的结果  
        Optional<Product> productOpt = productFuture.join();  
        Optional<Price> priceOpt = priceFuture.join();  
        Optional<Inventory> inventoryOpt = inventoryFuture.join();  

        int availableQuantity = inventoryOpt.map(Inventory::getAvailableQuantity).orElse(0);  
        double price = priceOpt.map(Price::getPrice).orElse(0.0);  

        // 构造并返回 ProductDetail 实例  
        return productOpt.map(product -> new ProductDetailDTO(productId, product.getCategory().getName(),  
                product.getName(), product.getDescription(), availableQuantity,  
                price, product.getStatus()));  
    }  
}  
步骤 5: 控制层

控制器提供了两个 API:一个同步的 API,一个异步的 API。

@RestController  
@RequestMapping("/api/v1/products")  
public class ProductController {  

    private static final Logger log = LoggerFactory.getLogger(ProductController.class);  
    private final ProductFacade productFacade;  
    private final ProductAsyncFacade productAsyncFacade;  

    public ProductController(ProductFacade productFacade, ProductAsyncFacade productAsyncFacade) {  
        this.productFacade = productFacade;  
        this.productAsyncFacade = productAsyncFacade;  
    }  

    @GetMapping("/{id}/sync")  
    public ResponseEntity<ProductDetailDTO> getProductSync(@PathVariable Long id) {  
        log.info("同步获取产品请求:{}", id);  
        return ResponseEntity.ok(productFacade.getProductDetail(id).orElseThrow());  
    }  

    @GetMapping("/{id}/async")  
    public ResponseEntity<ProductDetailDTO> getProductAsync(@PathVariable Long id) {  
        log.info("异步获取产品请求:{}", id);  
        return ResponseEntity.ok(productAsyncFacade.getProductDetailById(id).orElseThrow());  
    }  
}
第 6 步:模型类 (Model 类)。

ProductDetailDTO 类将包含产品、价格和库存的相关信息。

    @Data  
    @AllArgsConstructor  
    @NoArgsConstructor  
    // 商品详情数据传输对象
    public class ProductDetailDTO {  
        // 商品ID
        private Long id;  
        // 商品类别名称
        private String categoryName;  
        // 商品名称
        private String name;  
        // 商品描述
        private String description;  
        // 可用库存数量
        private Integer availableQuantity;  
        // 商品价格
        private Double price;  
        // 商品状态
        private String status;  
    }
第 7 步:响应时间比较

为了测试并比较这两个API的响应时间,我在同步服务端引入了Thread.sleep(2000)毫秒来模拟以现实世界的延迟。

同步API : 6.57秒
    curl --location 'http://localhost:8080/api/v1/products/101/sync'
这行命令用于同步产品ID为101的数据。

异步 API: 2.22 s

运行以下命令来获取异步产品数据:

    curl --location 'http://localhost:8080/api/v1/products/101/async'

观察记录

使用 CompletableFuture 的异步 API 通过并行执行显著缩短了响应时间。即使有 2 秒的延迟,异步版本仅耗时 2.22 秒,而同步版本则耗时 6.57 秒以上。

GitHub 代码库

你可以在我的 GitHub 仓库中找到这个例子的完整源代码文件,你可以在那里找到它。

https://github.com/palmurugan/api-performance (API性能测试)

结论

通过使用CompletableFuture,我们可以使API调用异步且非阻塞,这可以显著提升性能,特别是在涉及多个数据源的情况下更为明显。这种方法简单易实现,并提供明显的性能提升,因此,它成为优化慢速或资源密集型API的首选方案。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP