基于Spring Cloud Gateway,實(shí)現(xiàn)路由和負(fù)載均衡
Spring Cloud Gateway是基于Spring framework 5、Project Reactor和Spring Boot 2.0構(gòu)建的API網(wǎng)關(guān)實(shí)現(xiàn)。它取代了之前使用的spring-cloud-netflix-zuul。請?jiān)L問下面的鏈接了解更多詳情。
Spring Cloud Greenwich.RC1現(xiàn)已發(fā)布。(https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now#spring-cloud-netflix-projects-entering-maintenance-mode)
還有一些其他的API網(wǎng)關(guān)實(shí)現(xiàn),如Kong、Tyk、Apigee等,它們并不基于Spring Cloud。但是本討論完全基于Spring團(tuán)隊(duì)創(chuàng)建的開源Spring Cloud Gateway。
一、簡介
Spring Cloud Gateway是非阻塞式的,即它的設(shè)計(jì)、編寫方式絕不會阻塞主線程。相反,這些線程始終可以為請求提供服務(wù),并在后臺異步處理請求,一旦處理完成就返回響應(yīng)。
Spring Cloud Gateway提供以下幾個(gè)功能:
- 將應(yīng)用程序中所有服務(wù)的路由映射到單個(gè)URL。
- 構(gòu)建過濾器,可以檢查并處理通過網(wǎng)關(guān)發(fā)出的請求和響應(yīng)。
- 構(gòu)建謂詞,這些對象允許我們在執(zhí)行或處理請求之前檢查請求是否滿足一組給定的條件。
Spring Cloud Gateway是一個(gè)反向代理。反向代理是位于試圖訪問資源的客戶端和資源本身之間的中間服務(wù)器??蛻舳松踔敛恢雷约赫谂c服務(wù)器通信。反向代理負(fù)責(zé)捕獲客戶端的請求,然后代表客戶端調(diào)用遠(yuǎn)程資源。簡而言之,反向代理就像其他API網(wǎng)關(guān)一樣,充當(dāng)所有進(jìn)入系統(tǒng)的請求的單一入口點(diǎn),而系統(tǒng)則分為一個(gè)或多個(gè)微服務(wù)。
可以對網(wǎng)關(guān)進(jìn)行配置,以基于與DiscoveryClient兼容的服務(wù)注冊表中注冊的服務(wù)創(chuàng)建路由。要啟用此功能,我們需要在屬性文件中設(shè)置以下屬性,并確保DiscoveryClient實(shí)現(xiàn)位于類路徑上并已啟用(例如Netflix Eureka、Consul或Zookeeper)。
spring.cloud.gateway.discovery.locator.enabled=true
Spring Cloud Gateway現(xiàn)在將自動(dòng)使用被調(diào)用服務(wù)的Eureka服務(wù)ID,并將其映射到下游服務(wù)實(shí)例。
二、路由
路由可以通過Java配置或通過在屬性/YAML文件中配置來定義。在這里,為了簡單起見,我們將使用第二種方法,因?yàn)樗梢愿鶕?jù)需求進(jìn)行外部化。
spring.cloud.gateway.routes[0].id=product-service
spring.cloud.gateway.routes[0].uri=lb://product-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/product/**
spring.cloud.gateway.routes[1].id=inventory-service
spring.cloud.gateway.routes[1].uri=lb://inventory-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/inventory/**
三、負(fù)載均衡
當(dāng)存在多個(gè)可用實(shí)例時(shí),Spring Cloud Gateway將智能地在Discovery客戶端中的可用實(shí)例之間平衡傳入請求的負(fù)載。
它內(nèi)部使用spring-cloud-loadbalancer來分發(fā)請求流量。它使用其中一種算法來完成相同的操作,但是負(fù)載均衡算法的內(nèi)部實(shí)現(xiàn)超出了本討論的范圍,我們將在演示結(jié)束后的幾分鐘內(nèi)更詳細(xì)地討論它是如何實(shí)現(xiàn)的。
注意:這不能與使用Spring-cloud-loadbalancer的客戶端負(fù)載均衡混淆,后者需要在服務(wù)通過基于Spring的不同同步/異步Rest客戶端(如RestTemplate、WebClient等)相互通信時(shí)使用,與Spring Cloud的Open Feign不同,這些客戶端默認(rèn)情況下不進(jìn)行負(fù)載均衡。此外,當(dāng)請求到達(dá)單個(gè)服務(wù)之一時(shí),需要客戶端負(fù)載均衡,而在Spring Cloud Gateway中,請求仍在API Gateway層,不需要客戶端負(fù)載均衡。
好了,現(xiàn)在讓我們通過一個(gè)快速演示來說明這一點(diǎn)吧。
四、演示
在這個(gè)演示中,我們將創(chuàng)建兩個(gè)服務(wù),一個(gè)是產(chǎn)品服務(wù)(product service),一個(gè)是庫存服務(wù)(inventory service),并將它們的多個(gè)實(shí)例注冊到Netflix Eureka Discovery Server上。完成后,我們將創(chuàng)建API Gateway服務(wù)器,并將其注冊到Eureka Client中。
所有的服務(wù)都是使用Spring Boot 3.2.4和Spring Cloud 2023.0.0創(chuàng)建的。
所有服務(wù)啟動(dòng)并正常運(yùn)行后,我們可以在下面的Eureka儀表板上看到注冊的所有服務(wù),它們運(yùn)行在8761端口上,每個(gè)服務(wù)有多個(gè)(2個(gè))實(shí)例。
圖片
Eureka儀表板顯示所有已注冊服務(wù),運(yùn)行在8761端口上。
現(xiàn)在讓我們深入了解各個(gè)服務(wù)。為了展示Spring Cloud Gateway的路由和負(fù)載均衡功能,我們將盡量保持業(yè)務(wù)邏輯的最小化。
我們在每個(gè)服務(wù)中創(chuàng)建了一個(gè)/greet端點(diǎn),它是一個(gè)HTTP GET請求。為了展示負(fù)載均衡功能,我們在響應(yīng)體中發(fā)送以下字段:
- greeting:一個(gè)簡單的硬編碼問候消息。
- instanceid:實(shí)際注冊到Eureka Server的實(shí)例ID。
- port:實(shí)例的實(shí)際端口號(由Spring Boot Embedded Tomcat動(dòng)態(tài)創(chuàng)建)。
- url:端點(diǎn)的完整URL。
但是其中最重要的是端口,因?yàn)樗怯脕碜R別負(fù)載均衡功能的。為了獲取該值,我們可以在控制器層添加以下代碼片段,如下所示:
@Value("${spring.application.name}")
private String appName;
// 只有當(dāng)發(fā)現(xiàn)客戶端是Eureka時(shí)才有效
private EurekaClient eurekaClient;
// 連接Eureka客戶端
public ProductController(EurekaClient eurekaClient) {
this.eurekaClient = eurekaClient;
}
@GetMapping("/greet")
public ResponseEntity<GreetingResponse> getProduct(HttpServletRequest request) {
InstanceInfo service = eurekaClient.getApplication(appName).getInstances().get(0);
response.setPort(service.getPort());
return new ResponseEntity<>(response, HttpStatus.OK);
}
接下來,我們需要在API Gateway中配置路由。路由配置已在本文的路由部分中提到過。
一切就緒后,讓我們測試一下應(yīng)用程序。
五、測試
我們將需要一些REST API測試工具,如Postman或Insomnia。在這里使用了Insomnia。
Spring Cloud Gateway運(yùn)行在Spring Boot的默認(rèn)端口8080上。我們將訪問API Gateway,而不是直接調(diào)用產(chǎn)品/庫存服務(wù),這也是本演示的目的所在,看看我們是否能夠獲得響應(yīng)。
圖片
圖片
由于我們能夠獲得正確的響應(yīng),說明路由功能正常工作。
現(xiàn)在,讓我們來看看負(fù)載均衡方面的內(nèi)容。我們只選取庫存服務(wù)實(shí)例進(jìn)行演示。
在上面的截圖中,我們看到庫存服務(wù)的端口是58464,這意味著請求是從58464端口提供的。讓我們再次發(fā)出請求,看看我們是否會一直獲得相同的端口。
圖片
在多次訪問端點(diǎn)后,我們發(fā)現(xiàn)一些不同的端口號58436和一些不同的實(shí)例ID,這意味著請求是從另一個(gè)實(shí)例提供的,而不是同一個(gè)實(shí)例。這意味著它成功地分發(fā)了請求。
六、深入了解負(fù)載均衡
完成上述工作后,讓我們試著深入研究一下。我們從API Gateway服務(wù)器的日志開始,通過將日志級別設(shè)置為TRACE來查看發(fā)生了什么。
日志有很多,但最重要的是下面的內(nèi)容,它可以幫助我們窺探框架層的幕后運(yùn)行狀況。
圖片
因此,基本上,負(fù)載均衡的實(shí)際URL是在Spring Cloud Gateway jar文件的以下類中解析的:
https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java
日志分別來自第108行和第143行。現(xiàn)在,在所有的方法中,choose()方法是最重要的,它創(chuàng)建了負(fù)載均衡器的實(shí)例ReactLoadBalancer。ReactLoadBalancer是一個(gè)接口,來自于spring cloud loadbalancer,它為實(shí)際的負(fù)載均衡實(shí)現(xiàn)算法提供了一個(gè)抽象。目前有兩種可用的實(shí)現(xiàn)方式,即RandomLoadBalancer和RoundRobinLoadBalancer。關(guān)于它的工作原理和算法實(shí)現(xiàn)的更多內(nèi)部細(xì)節(jié)對于開發(fā)者來說并不重要,因?yàn)镾pring Cloud Gateway框架已經(jīng)在開箱即用時(shí)處理了這些細(xì)節(jié)。
但需要理解的一點(diǎn)是,如果沒有l(wèi)b方案,負(fù)載均衡將無法工作。在路由部分,我們在屬性文件的API Gateway服務(wù)中定義了以下配置。
spring.cloud.gateway.routes[1].uri=lb://inventory-service
如果URL具有l(wèi)b方案(即lb://myservice),它將使用Spring Cloud ReactorLoadBalancer將名稱解析為實(shí)際的主機(jī)和端口。因此,如果不添加lb方案,負(fù)載均衡將無法工作。
七、結(jié)語
這就是關(guān)于Spring Cloud Gateway中路由和負(fù)載均衡的討論。完整的代碼可以在以下的GitHub鏈接中找到。
https://github.com/purbarunc/Spring-Cloud-Microservice/tree/gateway-loadbalancing