微服務架構體系中,通常一個業務系統會有很多的微服務,比如:OrderService、ProductService、UserService...,爲了讓調用更簡單,一般會在這些服務前端再封裝一層,類似下面這樣:
前面這一層俗稱爲“網關層”,其存在意義在於,將"1對N"問題 轉換成了"1對1”問題,同時在請求到達真正的微服務之前,可以做一些預處理,比如:來源合法性檢測,權限校驗,反爬蟲之類...
傳統方式下,最土的辦法,網關層可以人肉封裝,類似以下示例代碼:
|
這樣做,當然能跑起來,但是維護量大,以後各個微服務增加了新方法,都需要在網關層手動增加相應的方法封裝,而spring cloud 中的zuul很好的解決了這一問題,示意圖如下:
Zuul做爲網關層,自身也是一個微服務,跟其它服務Service-1,Service-2, ... Service-N一樣,都註冊在eureka server上,可以相互發現,zuul能感知到哪些服務在線,同時通過配置路由規則(後面會給出示例),可以將請求自動轉發到指定的後端微服務上,對於一些公用的預處理(比如:權限認證,token合法性校驗,灰度驗證時部分流量引導之類),可以放在所謂的過濾器(ZuulFilter)裏處理,這樣後端服務以後新增了服務,zuul層幾乎不用修改。
使用步驟:
一、添加zuul依賴的jar包
1 |
|
二、application.yml裏配置路由
1 2 3 4 5 6 7 8 9 |
|
解釋一下:上面這段配置表示,/api-user/開頭的url請求,將轉發到service-provider這個微服務上,/api-order/開頭的url請求,將轉發到service-consumer這個微服務上。
三、熔斷處理
如果網關後面的微服務掛了,zuul還允許定義一個fallback類,用於熔斷處理,參考下面的代碼:
開發人員只要在getRoute這個方法裏指定要處理的微服務實例,然後重寫fallbackResponse即可。
此時,如果觀察/health端點,也可以看到hystrix處於融斷開啓狀態
四、ZuulFilter過濾器
過濾器是一個很有用的機制,下面分幾種經典場景演示下:
4.1、token校驗/安全認證
網關直接暴露在公網上時,終端要調用某個服務,通常會把登錄後的token傳過來,網關層對token進行有效性驗證,如果token無效(或沒傳token),提示重新登錄或直接拒絕。另外,網關後面的微服務,如果設置了spring security中的basic Auth(即:不允許匿名訪問,必須提供用戶名、密碼),也可以在Filter中處理。參考下面的代碼:
Filter一共有4種類型,其常量值在org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 中定義
安全校驗,一般放在請求真正處理之前,所以上面的示例filterType指定爲pre,剩下的只要在shouldFilter()、run()方法中重寫自己的邏輯即可。
4.2 動態修改請求參數
zuulFilter可以攔截所有請求參數,並對其進行修改,比如:終端發過來的數據,出於安全要求,可能是經過加密處理的,需要在網關層進行參數解密,再傳遞到後面的服務;再比如:用戶傳過來的token值,需要轉換成userId/userName這些信息,再傳遞到背後的微服務。參考下面的run方法:
更多filter的示例,可以參考官網:https://github.com/spring-cloud-samples/sample-zuul-filters
4.3 灰度發佈(Gated Launch/Gray Release)
大型分佈式系統中,灰度發佈是保證線上系統安全生產的重要手段,一般的做法爲:從集羣中指定一臺(或某幾臺)機器,每次做新版本發佈前,先只發布這些機器上,先觀察一下是否正常,如果穩定運行後,再發布到其它機器。這種策略(相當於按部分節點來灰度),大多數情況下可以滿足要求,但是有一些特定場景,可能不太適用。
比如:筆者所在的“美味不用等”公司,主要B端用戶爲各餐飲品牌的商家,多數情況下,如果新上了一個功能,希望找一些規模較小的餐廳做試點,先看看上線後的運行情況,如果運行良好,再推廣到其它商家。
再比如:後端服務有N多個版本在同時運行,比如V1、V2,現在新加了一個V3版本(這在手機app應用中很常見),希望只有部分升級了app的用戶訪問最新的V3版本服務,其它用戶仍然訪問舊版本,待系統穩定後,再大規模提示用戶升級。
對於這些看上去需求各異的灰度需求,其實本質是一樣的:將請求(根據參數內容+業務規則),將其轉向到特定的灰度機器上。Spring Cloud MicroService中有一個metadata-map(元數據)設置,可以很好的滿足這類需求。
首先要引入一個jar包:(這是github上開源的一個項目ribbon-discovery-filter-spring-cloud-starter)
1 |
|
示例如下:
在各個服務的application.yml中設置以下metadata-map
1 2 3 4 |
|
即:所有節點發布後,默認灰度模式爲false。然後把特定的灰度機器上的配置,該參數改成true(表明這臺機器是用於灰度驗證的)。
然後在ZuulFilter中參考下面的代碼:
注意18-23行,這裏演示了通過特定的token參數值,將請求引導到gated-lanuch=true的機器上。(注:參考這個原理,大家可以把參數值,換成自己的version-版本號,shopId-商家Id之類)。只要請求參數中的token=1234567890,這次請求就會轉發到灰度節點上。
如果有朋友好奇這是怎麼做到的,可以看下io.jmnarloch.spring.cloud.ribbon.predicate.MetadataAwarePredicate 這個類:
大致原理就是拿上下文中,開發人員設置的屬性 與 服務節點裏的metadata-map 進行比較,如果metadata-map中包括開發人員設置的屬性,就返回成功(即:選擇這臺服務器)