使用Spring WebFlux和Spring Cloud的反應式微服務

老K的Java博客 2021-09-20 02:42:43 阅读数:164

使用 spring webflux spring cloud

在Spring5正式發布之後,值得一看它的當前版本。此外,我們將嘗試將我們的反應式微服務放在Spring雲生態系統中,該生態系統包含諸如Eureka的服務發現、Spring Cloud [email protected]的負載平衡以及使用Spring Cloud gateway的API gateway(也基於WebFlux和Netty)等元素。我們還將通過SpringDatareactiveMongo項目的示例來檢查Spring對NoSQL數據庫的響應式支持。

下圖展示了示例系統的體系結構,該體系結構由兩個微服務組成:discovery server、gateway和MongoDB數據庫。源代碼與往常一樣可以在GitHub的SpringCloudWebFlux示例庫中獲得。

讓我們描述創建上述系統的進一步步驟。

第一步. 使用SpringWebFlux構建反應式應用程序

要為項目啟用庫SpringWebFlux,我們應該在依賴項中包含spring-boot-starter-webflux。它包括一些依賴庫,如Reactor或Netty服務器。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency>

REST控制器看起來非常類似於為同步web服務定義的控制器。唯一的區別在於返回對象的類型。我們返回的不是單個對象,而是Mono類的實例,我們返回的不是list類的實例,而是Flux類的實例。多虧了SpringDataResponsiveMongo,我們不必在存儲庫bean上調用所需的方法。

@RestControllerpublic class AccountController { private static final Logger LOGGER = LoggerFactory.getLogger(AccountController.class); @Autowired private AccountRepository repository; @GetMapping("/customer/{customer}") public Flux findByCustomer(@PathVariable("customer") String customerId) { LOGGER.info("findByCustomer: customerId={}", customerId); return repository.findByCustomerId(customerId); } @GetMapping public Flux findAll() { LOGGER.info("findAll"); return repository.findAll(); } @GetMapping("/{id}") public Mono findById(@PathVariable("id") String id) { LOGGER.info("findById: id={}", id); return repository.findById(id); } @PostMapping public Mono create(@RequestBody Account account) { LOGGER.info("create: {}", account); return repository.save(account); }}

第二步. 使用Spring Data Mongo將應用程序與數據庫集成

應用程序和數據庫集成的實現也非常簡單。首先,我們需要包括spring-boot-starter-data-mongodb-reactive,以響應項目依賴關系。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId></dependency>

對反應式Mongo存儲庫的支持在包含初學者後自動啟用。下一步是使用ORM映射聲明實體。AccountController還將以下類作為響應返回。

@Documentpublic class Account { @Id private String id; private String number; private String customerId; private int amount; ...}

最後,我們可以創建一個擴展ReactiveCrudepository的存儲庫接口。它遵循SpringDataJPA實現的模式,並為CRUD操作提供一些基本方法。它還允許您使用自動映射到查詢的名稱定義方法。與標准Spring數據JPA存儲庫相比,唯一的區別在於方法簽名。對象由MonoFlux包裹。

public interface AccountRepository extends ReactiveCrudRepository { Flux findByCustomerId(String customerId);}

在本例中,我使用Docker容器在本地運行MongoDB。因為我使用Docker Toolkit在Windows上運行Docker,Docker機器的默認地址是192.168.99.100。下面是application.yml文件中數據源的配置。

spring: data: mongodb: uri: mongodb://192.168.99.100/test

第三步. 使用Eureka啟用服務發現

與SpringCloudEureka的集成與同步REST微服務的集成非常相似。為了啟用discovery client,我們應該首先將spring-cloud-starter-netflix-eureka-client包含到項目依賴項中。

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>

然後我們必須使用@EnableDiscoveryClient注釋啟用它。

@[email protected] class AccountApplication { public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); }}

Microservice將自動在Eureka中注册。當然,我們可能會運行每個服務的多個實例。這是顯示Eureka儀錶板的屏幕(http://localhost:8761)運行兩個account-service實例和一個customer-service實例後。Eureka服務器作為發現服務模塊提供。

第四步. 反應式微服務與WebClient之間的服務間通信

服務間通信由SpringWebFlux項目的WebClient實現。與RestTemplate相同,您應該使用[email protected]對其進行注釋。它使用Netflix OSS Ribbon client實現與服務發現和負載平衡的集成。因此,第一步是用@LoadBalanced注釋聲明一個客戶端構建器bean。

@[email protected] WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder();}

然後我們可以將WebClient Builder注入REST控制器。與帳戶服務的通信是在GET/{id}/with accounts中實現的,在這裏我們首先使用reactive Spring數據存儲庫搜索客戶實體。它返回object Mono,而WebClient返回Flux。現在,我們的主要目標是將這些內容合並到發布者,並返回單個Mono對象以及從Flux獲取的帳戶列錶,而不阻塞流。下面的代碼片段演示了我如何使用WebClient與其他microservice通信,然後將存儲庫中的響應和結果合並到單個Mono對象。這種合並可能會以更“優雅”的方式進行,所以可以隨意使用您的提案創建推送請求。

@Autowiredprivate WebClient.Builder webClientBuilder;@GetMapping("/{id}/with-accounts")public Mono findByIdWithAccounts(@PathVariable("id") String id) { LOGGER.info("findByIdWithAccounts: id={}", id); Flux accounts = webClientBuilder.build().get().uri("http://account-service/customer/{customer}", id).retrieve().bodyToFlux(Account.class); return accounts .collectList() .map(a -> new Customer(a)) .mergeWith(repository.findById(id)) .collectList() .map(CustomerMapper::map);}

第五步. 使用Spring Cloud網關構建API網關

SpringCloudGateway是最新的SpringCloud項目之一。它構建在SpringWebFlux之上,正因為如此,我們可以使用它作為基於SpringBoot的反應式微服務的示例系統的網關。與SpringWebFlux應用程序類似,它在嵌入式Netty服務器上運行。要為SpringBoot應用程序啟用它,只需將以下依賴項包含到您的項目中。

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId></dependency>

我們還應該啟用發現客戶端,以便允許網關獲取已注册微服務的列錶。但是,不需要在Eureka中注册網關應用程序。要禁用注册,可以在application.yml文件中將屬性eureka.client.registerWithEureka設置為false

@[email protected] class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }}

默認情况下,SpringCloudGateway不支持與服務發現的集成。要啟用它,我們應該將屬性spring.cloud.gateway.discovery.locator.enabled設置為true。現在,應該做的最後一件事是路由的配置。SpringCloudGateway提供了兩種類型的組件,可以在路由內部配置:過濾器和謂詞。謂詞用於將HTTP請求與路由匹配,而過濾器可用於在發送下遊請求之前或之後修改請求和響應。下面是網關的完整配置。它啟用服務發現比特置,並根據服務注册錶中的條目定義兩條路由。我們使用路徑路由謂詞工廠來匹配傳入請求,使用重寫路徑網關過濾器工廠來修改請求的路徑,以使其適應下遊服務公開的格式(端點在Path/下公開,而網關在Path/account/customer下公開)。

spring: cloud: gateway: discovery: locator: enabled: true routes: - id: account-service uri: lb://account-service predicates: - Path=/account/** filters: - RewritePath=/account/(?.*), /$\{path} - id: customer-service uri: lb://customer-service predicates: - Path=/customer/** filters: - RewritePath=/customer/(?.*), /$\{path}

第六步. 使用Spring Boot測試反應式微服務

在進行一些測試之前,讓我們回顧一下我們的示例系統。我們有兩個微服務帳戶服務,客戶服務使用MongoDB作為數據庫。Microservice客戶服務調用由帳戶服務公開的端點GET/customer/{customer}。帳戶服務的URL取自Eureka。整個示例系統隱藏在網關後面,網關的地址為localhost:8090

現在,第一步是在Docker容器上運行MongoDB。執行以下命令後,Mongo在地址192.168.99.100:27017下可用。

$ docker run -d --name mongo -p 27017:27017 mongo

然後我們可以繼續運行發現服務。Eureka的默認地址為localhost:8761。您可以使用IDE運行它,或者只需執行命令java-jar target/discovery-service-1.0-SNAPHOT.jar即可。同樣的規則也適用於我們的示例微服務。但是,帳戶服務需要在兩個實例中相乘,因此在運行第二個實例時,需要使用-Dserver.port VM參數覆蓋默認HTTP端口,例如java-jar-Dserver.port=2223 target/account-service-1.0-SNAPSHOT.jar。最後,在運行網關服務之後,我們可以添加一些測試數據。

$ curl --header "Content-Type: application/json" --request POST --data '{"firstName": "John","lastName": "Scott","age": 30}' http://localhost:8090/customer{"id": "5aec1debfa656c0b38b952b4","firstName": "John","lastName": "Scott","age": 30,"accounts": null}$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567890","amount": 5000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account{"id": "5aec1e86fa656c11d4c655fb","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 5000}$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567891","amount": 12000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account{"id": "5aec1e91fa656c11d4c655fc","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 12000}$ curl --header "Content-Type: application/json" --request POST --data '{"number": "1234567892","amount": 2000,"customerId": "5aec1debfa656c0b38b952b4"}' http://localhost:8090/account{"id": "5aec1e99fa656c11d4c655fd","number": "1234567892","customerId": "5aec1debfa656c0b38b952b4","amount": 2000}

要測試服務間通信,只需使用網關服務上的帳戶調用端點GET/customer/{id}/。它將請求轉發給客戶服務,然後客戶服務使用反應式WebClient調用account-service公開的端點。結果如下所示。

結論

自Spring5和SpringBoot2.0以來,有一整套可用的方法來構建基於微服務的體系結構。我們可以使用Spring Cloud Netflix項目的一對一通信、基於message broker的消息傳遞微服務和Spring Cloud Stream的發布/訂閱通信模型,以及最終使用Spring WebFlux的异步、反應式微服務,構建標准的同步系統。本文的主要目標是向您展示如何將SpringWebFlux與SpringCloud項目結合使用,以便為構建在SpringBoot之上的反應式微服務提供服務發現、負載平衡或API網關等機制。在Spring5之前,缺少對反應式微服務的支持Spring引導支持是Spring框架的缺點之一,但現在使用SpringWebFlux就不再是這樣了。不僅如此,我們還可以利用Spring對最流行的NoSQL數據庫(如MongoDB或Cassandra)的反應式支持,輕松地將反應式微服務與同步REST微服務放在一個系統中。

原文地址:https://piotrminkowski.com/2018/05/04/reactive-microservices-with-spring-webflux-and-spring-cloud/

版权声明:本文为[老K的Java博客]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210920024243413z.html