SpringCloud實戰小貼士:Zuul的路徑匹配
路徑匹配
不論是使用傳統路由的配置方式還是服務路由的配置方式,我們都需要為每個路由規則定義匹配表達式,也就是上面所說的path參數。在Zuul中,路由匹配的路徑表達式采用了Ant風格定義。
Ant風格的路徑表達式使用起來非常簡單,它一共有下面這三種通配符:
通配符說明
我們可以通過下表的示例來進一步理解這三個通配符的含義并參考著來使用:
URL路徑說明
/user-service/?它可以匹配/user-service/之后拼接一個任務字符的路徑,比如:/user-service/a、/user-service/b、/user-service/c
/user-service/*它可以匹配/user-service/之后拼接任意字符的路徑,比如:/user-service/a、/user-service/aaa、/user-service/bbb。但是它無法匹配/user-service/a/b
/user-service/**它可以匹配/user-service/*包含的內容之外,還可以匹配形如/user-service/a/b的多級目錄路徑
另外,當我們使用通配符的時候,經常會碰到這樣的問題:一個URL路徑可能會被多個不同路由的表達式匹配上。比如:有這樣的一個場景,我們在系統建設的一開始實現了user-service服務,并且配置了如下路由規則:
- zuul.routes.user-service.path=/user-service/**
- zuul.routes.user-service.serviceId=user-service
但是隨著版本的迭代,我們對user-service服務做了一些功能拆分,將原屬于user-service服務的某些功能拆分到了另外一個全新的服務user-service-ext中去,而這些拆分的外部調用URL路徑希望能夠符合規則/user-service/ext/**,這個時候我們需要就在配置文件中增加一個路由規則,完整配置如下:
- zuul.routes.user-service.path=/user-service/**
- zuul.routes.user-service.serviceId=user-service
- zuul.routes.user-service-ext.path=/user-service/ext/**
- zuul.routes.user-service-ext.serviceId=user-service-ext
這個時候,調用user-service-ext服務的URL路徑實際上會同時被/user-service/**和/user-service/ext/**兩個表達式所匹配。在邏輯上,API網關服務需要優先選擇/user-service/ext/**路由,然后再匹配/user-service/**路由才能實現上述需求。但是如果使用上面的配置方式,實際上是無法保證這樣的路由優先順序的。
從下面的路由匹配算法中,我們可以看到它在使用路由規則匹配請求路徑的時候是通過線性遍歷的方式,在請求路徑獲取到***個匹配的路由規則之后就會返回并結束匹配過程。所以當存在多個匹配的路由規則時,匹配結果完全取決于路由規則的保存順序。
- @Override
- public Route getMatchingRoute(final String path) {
- ...
- ZuulRoute route = null;
- if (!matchesIgnoredPatterns(adjustedPath)) {
- for (Entry<String, ZuulRoute> entry : this.routes.get().entrySet()) {
- String pattern = entry.getKey();
- log.debug("Matching pattern:" + pattern);
- if (this.pathMatcher.match(pattern, adjustedPath)) {
- route = entry.getValue();
- break;
- }
- }
- }
- log.debug("route matched=" + route);
- return getRoute(route, adjustedPath);
- }
下面所示代碼是基礎的路由規則加載算法,我們可以看到這些路由規則是通過LinkedHashMap保存的,也就是說路由規則的保存是有序的,而內容的加載是通過遍歷配置文件中路由規則依次加入的,所以導致問題的根本原因是對配置文件中內容的讀取。
- protected Map<String, ZuulRoute> locateRoutes() {
- LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
- for (ZuulRoute route : this.properties.getRoutes().values()) {
- routesMap.put(route.getPath(), route);
- }
- return routesMap;
- }
由于properties的配置內容無法保證有序,所以當出現這種情況的時候,為了保證路由的優先順序,我們需要使用YAML文件來配置,以實現有序的路由規則,比如使用下面的定義:
- zuul:
- routes:
- user-service-ext:
- path: /user-service/ext/**
- serviceId: user-service-ext
- user-service:
- path: /user-service/**
- serviceId: user-service
忽略表達式
通過path參數定義的Ant表達式已經能夠完成API網關上的路由規則配置功能,但是為了更細粒度和更為靈活的配置路由規則,Zuul還提供了一個忽略表達式參數:zuul.ignored-patterns。該參數可以用來設置不希望被API網關進行路由的URL表達式。
比如,以快速入門中的示例為基礎,如果我們不希望/hello接口被路由,那么我們可以這樣設置:
- zuul.ignored-patterns=/**/hello/**
- zuul.routes.api-a.path=/api-a/**
- zuul.routes.api-a.serviceId=hello-service
然后,可以嘗試通過網關來訪問hello-service的/hello接口:http://localhost:5555/api-a/hello。雖然該訪問路徑的完全符合path參數定義的/api-a/**規則,但是由于該路徑符合zuul.ignored-patterns參數定義的規則,所以不會被正確路由。同時,我們在控制臺或日志中還能看到沒有匹配路由的輸出信息:
- o.s.c.n.z.f.pre.PreDecorationFilter : No route found for uri: /api-a/hello
另外,該參數在使用時還需要注意它的范圍并不是對某個路由,而是對所有路由的。所以在設置的時候需要全面的考慮URL規則,防止忽略了不該被忽略的URL路徑。
【本文為51CTO專欄作者“翟永超”的原創稿件,轉載請通過51CTO聯系作者獲取授權】