微服務Gateway
微服務網關部署在前端Nginx網關和後端微服務之間,Nginx一般充當流量網關,而微服務網關屬於一種業務型 網關,微服務網關層爲後端的微服務羣組提供統一的接入地址,其核心功能是統一做服務路由,在路由基礎上還 可以實現一些橫切性的功能,如:服務權鑑、協議轉換、日誌記錄、白名單控制等業務。
要創建一個Spring cloud gateway微服務網關,在項目中引入spring-cloud-starter-gateway
這個起始依賴即可。
Spring Cloud Gateway領域概念
- 路由(Route)
- 謂詞(Predicate)
- 過濾器(Filter)
“路由”是spring-cloud-gateway中的基本處理單元,定義一條路由規則,spring-cloud-gateway會加載 很多路由規則,但對於每個請求來說只會在其中一個路由上生效。
“謂詞”和“Filter”則是從屬於一個路由規則的。
謂詞就是一組斷言,用於對請求內容進行各種判斷匹配,一條路由中可以組合多個謂詞,只有全部謂詞斷言通過後, 路由才生效,多個謂詞條件用 and
操作符組合。
“Filter”則是對謂詞斷言通過後的請求內容以及響應內容進行加工,遵循責任鏈模式,可以用Filter來做如權鑑、 灰度、日誌等通用性功能。
Spring Cloud Gateway工作原理
謂詞
謂詞就是一個 java.util.function.Predicate
函數,進入網關的請求內容通過一個 ServerWebExchange
對象封裝,將其傳遞到 test
方法進行判斷。
謂詞由 RoutePredicateFactory
來定義,Spring cloud gateway內建了很多謂詞工廠:
- AfterRoutePredicateFactory 斷言請求發生時間在指定時間之後
- BeforeRoutePredicateFactory 斷言請求發生時間在指定時間之前
- BetweenRoutePredicateFactory 斷言請求發生在指定的兩個時間之間
- PathRoutePredicateFactory 斷言請求路徑符合給出的路徑模式列表
- MethodRoutePredicateFactory 斷言請求的HTTP動詞在指定的動詞列表中
- QueryRoutePredicateFactory 斷言指定的查詢參數的值符合給定的模式,模式由Java正則表達式給出
- HeaderRoutePredicateFactory 斷言指定請求頭的值符合指定的模式,模式由Java正則表達式給出
- CookieRoutePredicateFactory 斷言請求頭中包含指定的cookie
- ...
這些謂詞非常靈活,可以自由組合實現各種各樣的判斷效果,可以進入每個謂詞類中的 test
方法查看實現細節。
除了內建的謂詞,用戶也可以基於RoutePredicateFactory進行擴展自定義的謂詞。
Filter
Spring cloud gateway支持兩種filter:
GlobalFilter
GatewayFilter
GatewayFilter是局部的,從屬於某個路由規則。 GlobalFilter對所有路由規則都會生效,用於處理一些公共性的任務。
同樣的,運行時一個 ServerWebExchange
對象會傳遞到其 filter
方法。
GatewayFilter由GatewayFilterFactory
定義,同樣Spring cloud gateway也內建了各種各樣功能的GatewayFilter, 可以參考文檔對這些filter的說明:。
當一個請求匹配上某個路由規則後,Spring會把所有全局filter和局部filter都組合到一個FilterChain中,filter執行的 順序由其實現的getOrder
方法決定。
配置路由規則
Spring支持通過application.yml配置或者Java代碼配置兩種形式定義路由規則。
#在spring.cloud.gateway.routes 節點下配置即可
#採用yml或者properties配置時,是通過 RouteDefinitionLocator 來定義的
spring:
cloud:
gateway:
routes:
- id: "路由id"
uri: lb://order-svc #目標微服務
predicates: #指定謂詞列表,多個謂詞采用 and 邏輯組合
- Path="/**" # 匹配任何路徑
- Header="X-Is-Secure" #是否存在X-Is-Secure請求頭
filters: #指定gateway filter列表
- AddRequestHeader=X-Is-Secure,Yes #寫入請求頭 X-Is-Secure=Yes
採用java代碼定義路由規則就更靈活:
//java配置時通過 RouteLocator 來定義路由規則
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("r001",
predicate -> predicate
.path("/gateway/order/*")
.filters(filter -> filter.addRequestHeader("X-Is-Secure", "YES"))
.uri("lb://order-svc")
)
.route("r002",
predicate -> predicate.path("/gateway/product")
.and()
.header("...") //and組合
.filters(filterSpec -> filterSpec.stripPrefix(1))
.uri("lb://product-svc")
)
.build();
}
Java配置和properties(yaml)配置對比:
採用配置文件定義路由規則時,多個謂詞斷言採用and方式組合,而採用java代碼配置時,可以靈活選擇 and()
、or()
或者 negate()
不同的操作符組合。
配置文件定義路由是通過 RouteDefinitionLocator
來解析的,RouteDefinition
類是標準的用於定義 路由規則屬性的組件。而java配置方式則直接通過 RouteLocator
來定義路由規則。
還要注意,無論是java代碼配置,還是properties(yaml)配置,兩種形式定義的規則都是在程序啓動時就被解析並維護起來, 無法在運行時動態修改謂詞條件和過濾器處理邏輯。
路由規則的加載和持久化路由(RouteDefinitionRepository)
路由規則配置數據是由 RouteDefinitionLocator
負責加載的,路由規則存儲維護則由RouteDefinitionRepository
負責,RouteDefinitionRepository
接口擴展了RouteDefinitionLocator
接口,因此一個RouteDefinitionRepository
的實現者要實現路由規則加載和存儲維護兩個功能。
Spring cloud gateway爲RouteDefinitionRepository
提供了2個實現者:
- InMemoryRouteDefinitionRepository
- RedisRouteDefinitionRepository
InMemoryRouteDefinitionRepository
基於本地內存的策略,它被配置爲默認的路由規則存儲組件,由於是內存維護, 所以是易失的,程序只要重啓路由規則就丟失了。
RedisRouteDefinitionRepository
則是基於redis存儲的實現,這個是不易失的,因此可以作爲路由規則的持久化存儲 方式,在多實例gateway節點部署時,可以用RedisRouteDefinitionRepository
的方式來實現路由共享。
要開啓基於Redis的路由規則存儲機制,需要啓用屬性 spring.cloud.gateway.redis-route-definition-repository.enabled=true
。
實現動態路由(RouteDefinitionWriter)
運行時動態修改路由規則就靠RouteDefinitionWriter
這個組件,它暴露兩個操作,:
public interface RouteDefinitionWriter {
Mono<void> save(Mono<routedefinition> route); //修改路由規則
Mono<void> delete(Mono<string> routeId); //刪除路由規則
}
如果我們把路由規則維護在一個外部配置中心(nacos、zookeeper等),這類配置服務都支持實時推送內容變更,因此 我們可以實現一段監聽配置中心路由規則內容變更程序,它要依賴一個RouteDefinitionWriter
對象,當接收到配置中心 的變更推送時,就委託RouteDefinitionWriter
的save
或者delete
方法,這樣就實現了路由的動態變更和持久維護。
以Nacos爲例實現一個僞代碼:
//僞代碼,只表達意思
@Component
public class RouteDefinitionDynamicUpdater implements InitializingBean {
// 這個委託對象是InMemoryRouteDefinitionRepository,或者是RedisRouteDefinitionRepository都可以
@Autowired
private RouteDefinitionWriter delegateWriter;
@Override
public void afterPropertiesSet() {
ConfigService configService = initNacosConfigService();
//程序第一次啓動是獲取路由規則
String routeConfigStr = configService.getConfig(dataId, group);
List<routedefinition> routeDefinitionList = parseJson(routeConfigStr); //假設是json配置
for (RouteDefinition def : routeDefinitionList) {
delegateWriter.save(def);
}
//持續監聽nacos路由規則變更
configService.addListener(dataId, group, new Listener() {
public void receiveConfigInfo(String routeConfigStr) {
List<routedefinition> routeDefinitionList = parseJson(routeConfigStr); //假設是json配置
for (RouteDefinition def : routeDefinitionList) {
delegateWriter.save(def);
}
}
});
}
private ConfigService initNacosConfigService() {
//...
}
}
</routedefinition></routedefinition></string></void></routedefinition></void>