1. 簡介
服務目錄在重新整理 Invoker 清單的過程中,會通過 Router 進行服務路由,篩選出符合路由規則的服務提供者。在詳細分析服務路由的源碼之前,先來介紹一下服務路由是什麼。服務路由包含一條路由規則,路由規則決定了服務消費者的調用目标,即規定了服務消費者可調用哪些服務提供者。Dubbo 目前提供了三種服務路由實作,分别為條件路由 ConditionRouter、腳本路由 ScriptRouter 和标簽路由 TagRouter。其中條件路由是我們最常使用的,标簽路由是一個新的實作,暫時還未釋出,該實作預計會在 2.7.x 版本中釋出。本篇文章将分析條件路由相關源碼,腳本路由和标簽路由這裡就不分析了。
2. 源碼分析
條件路由規則由兩個條件組成,分别用于對服務消費者和提供者進行比對。比如有這樣一條規則:
host
該條規則表示 IP 為 10.20.153.10 的服務消費者隻可調用 IP 為 10.20.153.11 機器上的服務,不可調用其他機器上的服務。條件路由規則的格式如下:
[服務消費者比對條件] => [服務提供者比對條件]
如果服務消費者比對條件為空,表示不對服務消費者進行限制。如果服務提供者比對條件為空,表示對某些服務消費者禁用服務。官方文檔中對條件路由進行了比較詳細的介紹,大家可以參考下,這裡就不過多說明了。【加群】:857565362
條件路由實作類 ConditionRouter 在進行工作前,需要先對使用者配置的路由規則進行解析,得到一系列的條件。然後再根據這些條件對服務進行路由。本章将分兩節進行說明,2.1節介紹表達式解析過程。2.2 節介紹服務路由的過程。下面,我們先從表達式解析過程看起。
2.1 表達式解析
條件路由規則是一條字元串,對于 Dubbo 來說,它并不能直接了解字元串的意思,需要将其解析成内部格式才行。條件表達式的解析過程始于 ConditionRouter 的構造方法,下面一起看一下:
public
如上,ConditionRouter 構造方法先是對路由規則做預處理,然後調用 parseRule 方法分别對服務提供者和消費者規則進行解析,最後将解析結果指派給 whenCondition 和 thenCondition 成員變量。ConditionRouter 構造方法不是很複雜,這裡就不多說了。下面我們把重點放在 parseRule 方法上,在詳細介紹這個方法之前,我們先來看一個内部類。
private
MatchPair 内部包含了兩個 Set 類型的成員變量,分别用于存放比對和不比對的條件。這個類兩個成員變量會在 parseRule 方法中被用到,下面來看一下。
private
以上就是路由規則的解析邏輯,該邏輯由正規表達式和一個 while 循環以及數個條件分支組成。下面通過一個示例對解析邏輯進行演繹。示例為 host = 2.2.2.2 & host != 1.1.1.1 & method = hello。正則解析結果如下:
括号一 括号二
1. null host
2. = 2.2.2.2
3. & host
4. != 1.1.1.1
5. & method
6. = hello 現線上程進入 while 循環:
第一次循環:分隔符 separator = null,content = "host"。此時建立 MatchPair 對象,并存入到 condition 中,condition = {"host": [email protected]}
第二次循環:分隔符 separator = "=",content = "2.2.2.2",pair = [email protected]。此時将 2.2.2.2 放入到 [email protected] 對象的 matches 集合中。
第三次循環:分隔符 separator = "&",content = "host"。host 已存在于 condition 中,是以 pair = [email protected]。
第四次循環:分隔符 separator = "!=",content = "1.1.1.1",pair = [email protected]。此時将 1.1.1.1 放入到 [email protected] 對象的 mismatches 集合中。
第五次循環:分隔符 separator = "&",content = "method"。condition.get("method") = null,是以建立一個 MatchPair 對象,并放入到 condition 中。此時 condition = {"host": [email protected], "method": [email protected] 456}
第六次循環:分隔符 separator = "=",content = "2.2.2.2",pair = [email protected]。此時将 hello 放入到 [email protected] 對象的 matches 集合中。
循環結束,此時 condition 的内容如下:
路由規則的解析過程稍微有點複雜,大家可通過 ConditionRouter 的測試類對該邏輯進行測試。并且找一個表達式,對照上面的代碼走一遍,加深了解。
【加群】:857565362
2.2 服務路由
服務路由的入口方法是 ConditionRouter 的 router 方法,該方法定義在 Router 接口中。實作代碼如下:
public
router 方法先是調用 matchWhen 對服務消費者進行比對,如果比對失敗,直接傳回 Invoker 清單。如果比對成功,再對服務提供者進行比對,比對邏輯封裝在了 matchThen 方法中。下面來看一下這兩個方法的邏輯:
boolean
這兩個方法長的有點像,不過邏輯上還是有差别的,大家注意看。這兩個方法均調用了 matchCondition 方法,但它們所傳入的參數是不同的。這個需要特别注意一下,不然後面的邏輯不好弄懂。下面我們對這幾個參數進行溯源。matchWhen 方法向 matchCondition 方法傳入的參數為 [whenCondition, url, null, invocation],第一個參數 whenCondition 為服務消費者比對條件,這個前面分析過。第二個參數 url 源自 route 方法的參數清單,該參數由外部類調用 route 方法時傳入。比如:
private
上面這段代碼來自 RegistryDirectory,第二個參數表示的是服務消費者 url。matchCondition 的 invocation 參數也是從這裡傳入的。
接下來再來看看 matchThen 向 matchCondition 方法傳入的參數 [thenCondition, url, param, null]。第一個參數不用解釋了。第二個和第三個參數來自 matchThen 方法的參數清單,這兩個參數分别為服務提供者 url 和服務消費者 url。搞清楚這些參數來源後,接下來就可以分析 matchCondition 方法了。
private
如上,matchCondition 方法看起來有點複雜,這裡簡單說明一下。分割線以上的代碼實際上用于擷取 sampleValue 的值,分割線以下才是進行條件比對。條件比對調用的邏輯封裝在 isMatch 中,代碼如下:
private
isMatch 方法邏輯比較清晰,由三個條件分支組成,用于處理四種情況。這裡對四種情況下的比對邏輯進行簡單的總結,如下:
條件 過程 情況一 matches 非空,mismatches 為空 周遊 matches 集合元素,并與入參進行比對。隻要有一個元素成功比對入參,即可傳回 true。若全部失配,則傳回 false。 情況二 matches 為空,mismatches 非空 周遊 mismatches 集合元素,并與入參進行比對。隻要有一個元素成功比對入參,立即 false。若全部失配,則傳回 true。 情況三 matches 非空,mismatches 非空 優先使用 mismatches 集合元素對入參進行比對,隻要任一進制素與入參比對成功,就立即傳回 false,結束方法邏輯。否則再使用 matches 中的集合元素進行比對,隻要有任意一個元素比對成功,即可傳回 true。若全部失配,則傳回 false 情況四 matches 為空,mismatches 為空 直接傳回 false isMatch 方法是通過 UrlUtils 的 isMatchGlobPattern 方法進行比對,是以下面我們再來看看 isMatchGlobPattern 方法的邏輯。
public
以上就是 isMatchGlobPattern 兩個重載方法的全部邏輯,這兩個方法分别對普通的比對過程,以及”引用消費者參數“和通配符比對等特性提供了支援。這兩個方法的邏輯不是很複雜,且代碼中也進行了比較詳細的注釋,是以就不多說了。
3. 總結
本篇文章對條件路由的表達式解析和服務路由過程進行了較為細緻的分析。總的來說,條件路由的代碼還是有一些複雜的,需要靜下心來看。在閱讀條件路由代碼的過程中,要多調試。一般的架構都會有單元測試,Dubbo 也不例外,是以大家可以直接通過 ConditionRouterTest 對條件路由進行調試,無需重頭建構測試用例。
我這兒整理了比較全面的JAVA相關的面試資料,
需要領取面試資料的同學,請加群:473984645
擷取更多學習資料,可以加群:473984645或掃描下方二維碼