【SFA官方翻譯】Spring WebFlux和Spring Cloud進行響應式微服務開發
摘要:
如果你想用Spring的最新和最好的工具開始使用響應式微服務,那麽這篇文章就是為你準備的! 我已經在一年前的 Reactive microservices with Spring 5 這篇文章中描述了Spring對反應式的支持。當時,Spring WebFlux項目一直處於積極的發展階段,現在,在Spring 5正式發布之後,它在這個版本是非常值得關註的。 此外,我們將嘗試把反應式微服務放入Spring Cloud組件中,其中包含諸如Eureka服務發現,使用Spring Cloud Commons
@LoadBalanced
進行負載均衡以及使用Spring Cloud Gateway(也基於WebFlux和Netty)的API網關等元素,。 我們還將以Spring Data Reactive Mongo項目為例,介紹Spring對NoSQL數據庫的反應性支持。 我們的體系結構示例圖如下,它包含兩個微服務,一個服務發現,一個網關和MongoDB數據庫。 源代碼sample-spring-cloud-webflux通常在GitHub上的一樣可用。
接下來,我們進一步來描述創建上述系統的步驟。
第1步:使用Spring WebFlux構建反應式應用程序
為了在項目中使用庫Spring WebFlux,我們應該將 spring-boot-starter-webflux
添加到依賴關系中。 它包括一些依賴庫,如Reactor或Netty服務器。
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-webflux</artifactId>
-
</dependency>
REST控制器看起來與同步Web服務定義的控制器非常相似。 唯一的區別在於返回對象的類型。 如果我們返回單個對象,將使用Mono類的實例,如果我們返回的是多個對象,比如是一個列表,將返回一個Flux類的實例。 由於Spring Data Reactive Mongo
-
@RestController
-
public 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);
-
}
-
}
第2步:使用Spring Data Reactive 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的響應返回。
-
@Document
-
public class Account {
-
@Id
-
private String id;
-
private String number;
-
private String customerId;
-
private int amount;
-
...
-
}
最後,我們可以創建一個擴展ReactiveCrudRepository的接口。 它遵循Spring Data JPA實現的模式,並提供了一些CRUD操作的基本方法。 它還允許我們自定義方法,這些名稱會自動映射到查詢。 與標準Spring Data JPA庫相比唯一的區別在於方法簽名。 這些對象將由Mono和Flux進行包裝。
-
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
步驟3:使用Eureka啟用服務發現
與 SpringCloudEureka
的集成非常類似於傳統的REST微服務。 要啟用發現客戶端功能,我們應該首先將啟動器 spring-cloud-starter-netflix-eureka-client
添加到項目依賴項中。
-
<dependency>
-
<groupId>org.springframework.cloud</groupId>
-
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
-
</dependency>
然後我們必須使用 @EnableDiscoveryClient
這個註解來啟用它的功能。
-
@SpringBootApplication
-
@EnableDiscoveryClient
-
public class AccountApplication {
-
public static void main(String[] args) {
-
SpringApplication.run(AccountApplication.class, args);
-
}
-
}
微服務將自動在Eureka註冊中心進行註冊。 當然,我們可能會運行每個服務的多個實例。 以下是運行 account-service
實例和 customer-service
服務實例後的Eureka Dashboard 儀表板界面(http:// localhost:8761)。 這裏將不詳細講解使用嵌入式Eureka服務器運行應用程序的細節。 有關詳細信息,請參閱我之前的文章,Spring Boot 2.0的微服務快速指南,Eureka和Spring Cloud。 Eureka服務器可作為 adiscovery-service
模塊使用。
第4步:使用WebClient進行反應性微服務之間的服務間通信
Spring WebFlux項目中的WebClient實現了一個服務間通信。 與RestTemplate相同,您應該使用Spring Cloud Commons @LoadBalanced
對其進行註解。 它支持使用Netflix OSS Ribbon客戶端與服務發現和負載均衡進行集成。 所以,第一步是使用 @LoadBalanced
註解聲明一個客戶端構建器。
-
@Bean
-
@LoadBalanced
-
public WebClient.Builder loadBalancedWebClientBuilder() {
-
return WebClient.builder();
-
}
然後我們可以將WebClientBuilder註入到REST控制器中。 通過GET / {id} / with-accounts實現與 account-service
通信,首先我們使用一個基於響應式的Spring Data repository來搜索客戶實體。 它返回Mono對象,而WebClient返回Flux。 現在,我們的主要目的是將這些內容合並到訂閱者,並從Flux中返回一個包含帳戶列表的Mono對象。 下面的代碼片段說明了我如何使用WebClient與另一個微服務進行通信,然後將響應和結果合並到單個Mono對象。 這種合並可以用更“優雅”的方式完成,所以你可以隨意創建一個推送請求。
-
@Autowired
-
private 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);
-
}
第5步:使用Spring Cloud Gateway構建API網關
Spring Cloud Gateway是最新的Spring Cloud項目之一。 它建立在Spring WebFlux的基礎之上,並且由於這一點,我們可以將它用作基於反應式微服務的入口。 與Spring WebFlux應用程序類似,它在嵌入式Netty服務器上運行。 要使用Spring Boot應用程序啟用它,只需在您的項目中包含以下依賴項。
-
<dependency>
-
<groupId>org.springframework.cloud</groupId>
-
<artifactId>spring-cloud-starter-gateway</artifactId>
-
</dependency>
我們還應該啟用發現客戶端,以便網關能夠獲取已註冊的微服務列表。 但是,不需要在Eureka中註冊網關的應用程序。 要禁用註冊,可以在application.yml文件中將屬性 eureka.client.registerWithEureka
設置為 false
。
-
@SpringBootApplication
-
@EnableDiscoveryClient
-
public class GatewayApplication {
-
public static void main(String[] args) {
-
SpringApplication.run(GatewayApplication.class, args);
-
}
-
}
默認情況下,Spring Cloud Gateway不支持與服務發現的集成。要啟用它,我們應該將屬性 spring.cloud.gateway.discovery.locator.enabled
設置為true。現在,應該完成的最後一件事情就是路由器的配置。 Spring Cloud Gateway提供了兩種可以在路由中配置的組件:filters(過濾器)和predicates(謂詞)。 Predicates用於將HTTP請求與路由進行匹配,而過濾器可用於在發送請求之前或之後修改請求和響應。這是網關的完整配置。它啟用服務發現位置,並根據服務註冊表中的條目定義兩種路由。我們使用Path Route Predicate工廠來匹配傳入的請求,並使用RewritePath GatewayFilter工廠來修改請求的路徑,以使其匹配相應的服務格式(端點顯示在路徑/下,而網關將它們暴露在路徑下/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}
第6步:測試樣本系統
在做一些測試之前,讓我們回顧一下我們的示例系統。 我們有兩個微服務 - account-service
, customer-service
- 使用MongoDB作為數據庫。 微服務customer-service調用account-service暴露的端點GET / customer / {customer}。 account-service的URL來自Eureka。 整個系統隱藏在網關後面,該網關位於localhost:8090的地址下。 現在,第一步是在Docker容器上運行MongoDB。 執行以下命令後,Mongo在地址192.168.99.100:27017下可用。
-
$ docker run -d --name mongo -p 27017:27017 mongo
然後我們可以繼續運行 discovery-service
。 Eureka在其默認地址localhost:8761下可用。 您可以使用IDE運行它,或者執行命令 java-jar target/discovery-service-1.0-SNAPHOT.jar
。 同樣的適用於我們的示例微服務。 但是,account-service需要在兩個實例中進行通信,所以當使用-Dserver.port VM參數運行第二個實例時,您需要覆蓋默認的HTTP端口,例如 java-jar-Dserver.port=2223target/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}/with-accounts
。 它將請求轉發給customer-service,然後customer-service使用響應式WebClient調用由account-service 暴露的端點。 結果如下所示。
結論
Spring 5和Spring Boot 2.0的出現,帶來了許多可用的方法來構建基於微服務的體系結構。我們可以使用與Spring Cloud Netflix項目的一對一通信,基於消息代理的消息傳遞微服務,以及與Spring Cloud Stream的發布/訂閱通信模型構建標準同步系統,最後使用Spring實現異步,反應式微服務WebFlux。本文的主要目標是向您展示如何將Spring WebFlux與Spring Cloud項目一起使用,以便為構建在Spring Boot之上的響應式微服務提供服務發現,負載均衡或API網關等機制。在Spring 5之前,缺乏對響應式微服務的支持是Spring框架的缺點之一,但現在,Spring WebFlux已不再是這種情況。不僅如此,我們還可以利用Spring對最受歡迎的NoSQL數據庫(如MongoDB或Cassandra)的反應式支持,並輕松地將我們的反應式微服務與同步REST微服務一起放入一個系統中。
【SFA官方翻譯】Spring WebFlux和Spring Cloud進行響應式微服務開發