文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何用Spring WebFlux构建Reactive REST API

2024-12-11 19:47

关注

在本文中,我们将讨论如何使用Spring WebFlux来构建响应式REST API。在正式讨论之前,让我们首先来看看系统的开发,传统REST在实现中遇到的问题,以及当前API的普遍需求。

下图简要地罗列了传统应用和现代应用系统的主要特点。如今的系统讲求的是:分布式应用、云原生、高可用性和可扩展性。因此,有效地利用系统现有的资源是至关重要的。

 

应用程序API需求的演变

那么传统的REST API请求处理又是如何工作的呢?

 

传统REST API模型

如上图所示,传统REST API会带来如下问题:

下面,让我们来看看响应式API的优势,以及如何使用响应式编程,来解决上述问题。

那么,响应式编程的具体流程是怎样的呢?如下图所示,一旦应用程序调用了从某个数据源获取数据的操作,那么就会立即返回一个线程,并且让来自该数据源的数据作为数据/事件流出现。在此,应用程序是订阅者(subscriber),数据源是发布者(publisher)。一旦数据流完成后,onComplete事件就会被触发。 

 

数据流工作流程

如下图所示,如果发生了任何异常情况,发布者将会触发onError事件。 

数据流工作流程

在某些情况下,例如:从数据库中删除一个条目,发布者只会立即触发onComplete/onError事件,而不会调用onNext事件,毕竟没有任何数据可以返回。 

数据流工作流程

下面,我们进一步讨论:什么是背压,以及如何将背压应用于响应流。例如,我们有一个客户端应用正在向另一个服务请求数据。该服务能够以1000 TPS(吞吐量)的速率发布事件,而客户端应用只能以200 TPS的速率处理事件。

那么在这种情况下,客户端应用程序需要通过缓冲数据来进行处理。而在随后的调用中,客户端应用程序可能会缓冲更多的数据,以致最终耗尽内存。显然,这对于那些依赖该客户端应用的其他程序,会造成级联效应。为了避免此类情况,客户端应用可以要求服务在事件的末尾进行缓冲,并以客户端应用的速率去推送各种事件。这就是所谓的背压,具体流程请见下图。 

背压示例

下面,我们将介绍响应流的规范(请参见--https://www.reactive-streams.org/),以及一个实现案例--Project Reactor(请参见--https://projectreactor.io/)。通常,响应流的规范定义了如下接口类型:

  1. public interface Publisher {    
  2.      public void subscribe(Subscriber s); 
  3.  
  1. public interface Subscriber { 
  2.     public void onSubscribe(Subscription s); 
  3.      public void onNext(T t); 
  4.      public void onError(Throwable t); 
  5.      public void onComplete(); 
  6.  
  1. public interface Subscription { 
  2.     public void request(long n); 
  3.     public void cancel(); 
  4. }

下面是响应流规范的类图: 

响应流规范

其实,响应流规范具有许多种实现方式,上述Project Reactor只是其中的一种。Reactor可以完全实现无阻塞、且有效的请求管理。它能够提供两个响应式和可组合的API,即:Flux [N](请参见-- https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html)和Mono [0|1](请参见--https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html)。它们广泛地实现了响应式扩展(Reactive Extensions)。ReactorHTTP(包括Websocket)提供了非阻塞的背压式网络引擎、TCPUDP。它也非常适合于微服务的架构。

图片来源:https://projectreactor.io

图片来源:https://projectreactor.io

由于Reactor的实施往往涉及到Spring 5.x,因此,我们可以使用带有Spring servlet栈的命令式编程,来构建REST API。下图展示了Spring如何支持响应式和servlet栈的实现。 

 

图片来源:spring.io

下面是一个公布了响应式REST API的应用。在该应用中,我们使用到了:

下图是该应用的整体架构: 

下面是build.gradle文件的Groovy代码,它包含了与Spring WebFlux协同使用的各种依赖项。

  1. plugins { 
  2.      id 'org.springframework.boot' version '2.2.6.RELEASE' 
  3.      id 'io.spring.dependency-management' version '1.0.9.RELEASE' 
  4.      id 'java' 
  5. group = 'org.smarttechie' 
  6. version = '0.0.1-SNAPSHOT' 
  7. sourceCompatibility = '1.8' 
  8. repositories { 
  9.     mavenCentral() 
  10. }  
  11. dependencies { 
  12.    implementation 'org.springframework.boot:spring-boot-starter-data-cassandra-reactive' 
  13.    implementation 'org.springframework.boot:spring-boot-starter-webflux' 
  14.    testImplementation('org.springframework.boot:spring-boot-starter-test') { 
  15.    exclude group'org.junit.vintage', module: 'junit-vintage-engine' 
  16.    } 
  17.    testImplementation 'io.projectreactor:reactor-test' 
  18.  } 
  19. test { 
  20.   useJUnitPlatform() 
  21.  

在此应用程序中,我公布了如下API。您可以通过GitHub的相关链接--https://github.com/2013techsmarts/Spring-Reactive-Examples,下载源代码。  

在构建响应式API时,我们可以使用功能性样式编程模型来构建API,而无需使用RestController。当然,您需要具有如下的routerhandler组件:

Router 

  1. package org.smarttechie.router; 
  2. import org.smarttechie.handler.ProductHandler; 
  3. import org.springframework.context.annotation.Bean; 
  4. import org.springframework.context.annotation.Configuration; 
  5. import org.springframework.http.MediaType; 
  6. import org.springframework.web.reactive.function.server.RouterFunction; 
  7. import org.springframework.web.reactive.function.server.RouterFunctions; 
  8. import org.springframework.web.reactive.function.server.ServerResponse; 
  9. import static org.springframework.web.reactive.function.server.RequestPredicates.*; 
  10. @Configuration 
  11. public class ProductRouter { 
  12.      
  13.     @Bean 
  14. public RouterFunction    productsRoute(ProductHandler productHandler){ 
  15.     return RouterFunctions.route(GET("/products").and(accept(MediaType.APPLICATION_JSON)) ,productHandler::getAllProducts).andRoute(POST("/product").and(accept(MediaType.APPLICATION_JSON)),productHandler::createProduct).andRoute(DELETE("/product/{id}").and(accept(MediaType.APPLICATION_JSON)) ,productHandler::deleteProduct).andRoute(PUT("/product/{id}").and(accept(MediaType.APPLICATION_JSON)),productHandler::updateProduct); 
  16.  } 
  17. }

 Handler 

  1. package org.smarttechie.handler; 
  2.   import org.smarttechie.model.Product; 
  3.   import org.smarttechie.service.ProductService; 
  4.   import org.springframework.beans.factory.annotation.Autowired; 
  5.   import org.springframework.http.MediaType; 
  6.   import org.springframework.stereotype.Component;    
  7.   import org.springframework.web.reactive.function.server.ServerRequest; 
  8.    import org.springframework.web.reactive.function.server.ServerResponse; 
  9.   import reactor.core.publisher.Mono; 
  10.    import static org.springframework.web.reactive.function.BodyInserters.fromObject;     
  11.    @Component 
  12.    public class ProductHandler { 
  13.     @Autowired 
  14.     private ProductService productService; 
  15.      static Mono notFound = ServerResponse.notFound().build(); 
  16.     
  17.     public Mono getAllProducts(ServerRequest serverRequest) { 
  18.          return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(productService.getAllProducts(), Product.class); 
  19.   } 
  20.  
  21.      
  22.     public Mono createProduct(ServerRequest serverRequest) { 
  23.         Mono productToSave = serverRequest.bodyToMono(Product.class); 
  24.         return productToSave.flatMap(product -> 
  25.                 ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(productService.save(product), Product.class));   
  26.  
  27.    }    
  28.  
  29.         
  30.     public Mono deleteProduct(ServerRequest serverRequest) { 
  31.         String id = serverRequest.pathVariable("id");  
  32.         Mono deleteItem = productService.deleteProduct(Integer.parseInt(id)); 
  33.          return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(deleteItem, Void.class);   
  34.    } 
  35.      
  36.     public Mono updateProduct(ServerRequest serverRequest) { 
  37.       return productService.update(serverRequest.bodyToMono(Product.class)).flatMap(product ->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(product))) .switchIfEmpty(notFound);  
  38.     } 

至止,我们已经对如何公布响应式REST API有所了解。针对上述实现,我们使用了Gatling(译者注:是一款功能强大的负载测试工具),在响应式API和非响应式API(使用Spring RestController构建非响应式API)上,进行了简单的基准化测试。其结果比较如下图所示。具体的Gatling负载测试脚本,请参考GitHub上的链接:https://github.com/2013techsmarts/Spring-Reactive-Examples 

负载测试结果比较 

Build Reactive REST APIs With Spring WebFlux ,作者:Siva Prasad Rao Janapati

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

 

来源:Linux中国内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯