1. 程式人生 > >Spring Cloud構建企業級匯流排-第六部分服務閘道器

Spring Cloud構建企業級匯流排-第六部分服務閘道器

 
    前面的文章我們介紹了,Eureka用於服務的註冊於發現,Feign支援服務的呼叫以及均衡負載,Hystrix處理服務的熔斷防止故障擴散,Spring Cloud Config服務叢集配置中心,似乎一個微服務框架已經完成了。
    我們還是少考慮了一個問題,外部的應用如何來訪問內部各種各樣的微服務呢?在微服務架構中,後端服務往往不直接開放給呼叫端,而是通過一個API閘道器根據請求的url,路由到相應的服務。當新增API閘道器後,在第三方呼叫端和服務提供方之間就建立了一面牆,這面牆直接與呼叫方通訊進行許可權控制,後將請求均衡分發給後臺服務端。


為什麼需要API Gateway

  • 簡化客戶端呼叫複雜度
    在微服務架構模式下後端服務的例項數一般是動態的,對於客戶端而言很難發現動態改變的服務例項的訪問地址資訊。因此在基於微服務的專案中為了簡化前端的呼叫邏輯,通常會引入API Gateway作為輕量級閘道器,同時API Gateway中也會實現相關的認證邏輯從而簡化內部服務之間相互呼叫的複雜度。
 


  • 資料裁剪以及聚合
      通常而言不同的客戶端對於顯示時對於資料的需求是不一致的,比如手機端或者Web端又或者在低延遲的網路環境或者高延遲的網路環境。
     因此為了優化客戶端的使用體驗,API Gateway可以對通用性的響應資料進行裁剪以適應不同客戶端的使用需求。同時還可以將多個API呼叫邏輯進行聚合,從而減少客戶端的請求數,優化客戶端使用者體驗。


  • 多渠道支援
     當然我們還可以針對不同的渠道和客戶端提供不同的API Gateway,對於該模式的使用由另外一個大家熟知的方式叫Backend for front-end, 在Backend for front-end模式當中,我們可以針對不同的客戶端分別建立其BFF,進一步瞭解BFF可以參考這篇文章:Pattern: Backends For Frontends
 

  • 遺留系統的微服務化改造
      對於系統而言進行微服務改造通常是由於原有的系統存在或多或少的問題,比如技術債務,程式碼質量,可維護性,可擴充套件性等等。API Gateway的模式同樣適用於這一類遺留系統的改造,通過微服務化的改造逐步實現對原有系統中的問題的修復,從而提升對於原有業務響應力的提升。通過引入抽象層,逐步使用新的實現替換舊的實現。
 

Zuul介紹

介紹

    服務閘道器是微服務架構中一個不可或缺的部分。通過服務閘道器統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了許可權控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將許可權控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務叢集主體能夠具備更高的可複用性和可測試性。
    Zuul是提供動態路由,監控,彈性,安全等的邊緣服務。Zuul 相當於是裝置和Netflix流應用的Web網站後端所有請求的前門。Zuul可以適當的對多個Amazon Auto Scaling Groups進行路由請求。


zuul執行流程


   通過圖片可以清晰看出執行過程,在微服務中後端各種引用,利用zuul進行合理呼叫還是很有必要的,例如 負載、限流、監控、安全等等功能。

使用例項

準備工作

     在使用Zuul之前,我們先構建一個服務註冊中心、以及兩個簡單的服務,比如:我構建了一個service-A,一個service-B。然後啟動eureka-server和這兩個服務。通過訪問eureka-server,我們可以看到service-A和service-B已經註冊到了服務中心。
 

開始使用Zuul

    引入依賴spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通過指定serviceId的方式,eureka依賴不需要,但是為了對服務叢集細節的透明性,還是用serviceId來避免直接引用url的方式吧。
 

應用主類使用@EnableZuulProxy註解開啟Zuul

@EnableZuulProxy
@SpringCloudApplication
public class Application {
	public static void main(String[] args) {
		new SpringApplicationBuilder(Application.class).web(true).run(args);
	}
	@Bean
	public AccessFilter accessFilter() {
		return new AccessFilter();
	}
}
   這裡用了@SpringCloudApplication註解,之前沒有提過,通過原始碼我們看到,它整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,主要目的還是簡化配置。這幾個註解的具體作用這裡就不做詳細介紹了,之前的文章已經都介紹過。
application.properties中配置Zuul應用的基礎資訊,如:應用名、服務埠等。
 
spring.application.name=api-gateway
server.port=5555

Zuul配置

    完成上面的工作後,Zuul已經可以運行了,但是如何讓它為我們的微服務叢集服務,還需要我們另行配置,下面詳細的介紹一些常用配置內容。
服務路由
    通過服務路由的功能,我們在對外提供服務的時候,只需要通過暴露Zuul中配置的呼叫地址就可以讓呼叫方統一的來訪問我們的服務,而不需要了解具體提供服務的主機資訊了。
在Zuul中提供了兩種對映方式:
 
# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/
    該配置定義了,所有到Zuul的中規則為:/api-a-url/**的訪問都對映到http://localhost:2222/上,也就是說當我們訪問http://localhost:5555/api-a-url/add?a=1&b=2的時候,Zuul會將該請求路由到:http://localhost:2222/add?a=1&b=2上。
     其中,配置屬性zuul.routes.api-a-url.path中的api-a-url部分為路由的名字,可以任意定義,但是一組對映關係的path和url要相同,下面講serviceId時候也是如此。
    通過url對映的方式對於Zuul來說,並不是特別友好,Zuul需要知道我們所有為服務的地址,才能完成所有的對映配置。而實際上,我們在實現微服務架構時,服務名與服務例項地址的關係在eureka server中已經存在了,所以只需要將Zuul註冊到eureka server上去發現其他服務,我們就可以實現對serviceId的對映。例如,我們可以如下配置
# routes to serviceId
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A

zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
    針對我們在準備工作中實現的兩個微服務service-A和service-B,定義了兩個路由api-a和api-b來分別對映。另外為了讓Zuul能發現service-A和service-B,也加入了eureka的配置。
    接下來,我們將eureka-server、service-A、service-B以及這裡用Zuul實現的服務閘道器啟動起來,在eureka-server的控制頁面中,我們可以看到分別註冊了service-A、service-B以及api-gateway

嘗試通過服務閘道器來訪問service-A和service-B,根據配置的對映關係,分別訪問下面的url
  • http://localhost:5555/api-a/add?a=1&b=2:通過serviceId對映訪問service-A中的add服務 
  • http://localhost:5555/api-b/add?a=1&b=2:通過serviceId對映訪問service-B中的add服務 

  • http://localhost:5555/api-a-url/add?a=1&b=2:通過url對映訪問service-A中的add服務
       推薦使用serviceId的對映方式,除了對Zuul維護上更加友好之外,serviceId對映方式還支援了斷路器,對於服務故障的情況下,可以有效的防止故障蔓延到服務閘道器上而影響整個系統的對外服務
  • 服務過濾
     在完成了服務路由之後,我們對外開放服務還需要一些安全措施來保護客戶端只能訪問它應該訪問到的資源。所以我們需要利用Zuul的過濾器來實現我們對外服務的安全控制。     在服務閘道器中定義過濾器只需要繼承ZuulFilter抽象類實現其定義的四個抽象函式就可對請求進行攔截與過濾。比如下面的例子,定義了一個Zuul過濾器,實現了在請求被路由之前檢查請求中是否有accessToken引數,若有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。自定義過濾器的實現,需要繼承ZuulFilter,需要重寫實現下面四個方法:    filterType:返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾器型別,具體如下:i.pre:可以在請求被路由之前呼叫ii.routing:在路由請求時候被呼叫iii.post:在routing和error過濾器之後被呼叫iv.error:處理請求時發生錯誤時被呼叫    filterOrder:通過int值來定義過濾器的執行順序    shouldFilter:返回一個boolean型別來判斷該過濾器是否要執行,所以通過此函式可實現過濾器的開關。在上例中,我們直接返回true,所以該過濾器總是生效。    run:過濾器的具體邏輯。需要注意,這裡我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,然後通過ctx.setResponseStatusCode(401)設定了其返回的錯誤碼,當然我們也可以進一步優化我們的返回,比如,通過ctx.setResponseBody(body)對返回body內容進行編輯等。在實現了自定義過濾器之後,還需要例項化該過濾器才能生效,我們只需要在應用主類中增加如下內容:
@EnableZuulProxy
@SpringCloudApplication
public class Application {
	public static void main(String[] args) {
		new SpringApplicationBuilder(Application.class).web(true).run(args);
	}
	@Bean
	public AccessFilter accessFilter() {
		return new AccessFilter();
	}
} 
啟動該服務閘道器後,訪問:http://localhost:5555/api-a/add?a=1&b=2:返回401錯誤http://localhost:5555/api-a/add?a=1&b=2&accessToken=token:正確路由到server-A,並返回計算內容

總結

    不僅僅實現了路由功能來遮蔽諸多服務細節,更實現了服務級別、均衡負載的路由。實現了介面許可權校驗與微服務業務邏輯的解耦。通過服務閘道器中的過濾器,在各生命週期中去校驗請求的內容,將原本在對外服務層做的校驗前移,保證了微服務的無狀態性,同時降低了微服務的測試難度,讓服務本身更集中關注業務邏輯的處理。    實現了斷路器,不會因為具體微服務的故障而導致服務閘道器的阻塞,依然可以對外服務。