Stream中的Peek操作
前言
大家好,今天開始給大家分享 — Dubbo 專題之 Dubbo 路由規則之條件路由。在前一個章節中我們介紹了 Dubbo 令牌驗證和優雅停機,以及我們也例舉了常見的使用場景並且進行了原始碼解析來分析其實現原理,同時知道 Dubbo 中的令牌驗證核心思想就是透過服務提供端提供的token
或者隨機產生的token
放入註冊中心進行管理,然後服務消費端獲取token
令牌並且在呼叫服務提供端時攜帶 token
,服務提供端根據消費端攜帶的token
進行驗證。有的小夥伴可能會想:我們多個服務提供者能否透過一定的規則對呼叫的服務提供者進行過濾和限制呢?那接下來我們就圍繞著這個問題一起來學習下 Dubbo 中的路由規則。下面就讓我們快速開始吧!
1. 條件路由簡介
首先我們得了解什麼是路由規則?假設有這樣一個場景如下圖所示:
上圖中我們可以看到有兩個機房分別是機房A、機房B,其中機房A只能訪問到 Service A 和 Service B ,而機房B 只能訪問到 Service C 和 Service D。要實現上面這種場景我們就需要用到所謂的路由規則。路由規則是在發起一次RPC呼叫前過濾目標伺服器地址,而過濾後的地址列表,將作為消費端最終發起RPC呼叫的備選地址。在 Dubbo 中支援兩種路由規則今天我們主要討論條件路由。
- 條件路由:支援以介面服務或消費者應用為粒度配置路由規則。
2. 使用方式
下面我們簡單的討論下條件路由使用方式:
條件路由
-
介面服務粒度
# demo-consumer1 的消費者只能消費所有埠為20880的服務例項 # demo-consumer2 的消費者只能消費所有埠為20881的服務例項 --- scope: application #應用粒度 force: true runtime: true enabled: true key: demo-provider conditions: - application=demo-consumer1 => address=*:20880 - application=demo-consumer2 => address=*:20881
-
應用粒度
# BookFacade 的 queryAll 方法只能消費所有埠為20880的服務例項 # BookFacade 的 queryByName 方法只能消費所有埠為20881的服務例項 --- scope: service #服務粒度 force: true runtime: true enabled: true key: com.muke.dubbocourse.common.api.BookFacade conditions: - method=queryAll => address=*:20880 - method=queryByName => address=*:20881
-
欄位說明:
編號 欄位名稱 說明 必填 1 scope 路由規則的作用粒度,scope的取值會決定key的取值。
service 服務粒度 application 應用粒度。必填 2 Key 明確規則體作用在哪個介面服務或應用。 scope=service時,
key取值為[{group}:]{service}[:{version}]的組合 scope=application時,
key取值為application名稱 。必填 3 enabled enabled=true
當前路由規則是否生效,,預設生效。可不填 4 force force=false
當路由結果為空時,是否強制執行,如果不強制執行,
路由結果為空的路由規則將自動失效,預設為false
。可不填 5 runtime runtime=false
是否在每次呼叫時執行路由規則,
否則只在提供者地址列表變更時預先執行並快取結果,
呼叫時直接從快取中獲取路由結果。如果用了引數路由,必須設為true
,
需要注意設定會影響呼叫的效能,預設為false
。可不填 6 priority priority=1
路由規則的優先順序,用於排序,優先順序越大越靠前執行,預設為0
。可不填 7 conditions 定義具體的路由規則內容。 必填 -
Conditions規則體
格式:
-
=>
之前的為消費者匹配條件,所有引數和消費者的 URL 進行對比,當消費者滿足匹配條件時,對該消費者執行後面的過濾規則。 -
=>
之後為提供者地址列表的過濾條件,所有引數和提供者的 URL 進行對比,消費者最終只拿到過濾後的地址列表。 - 如果匹配條件為空,表示對所有消費方應用,如:
=> host != 192.168.53.11
- 如果過濾條件為空,表示禁止訪問,如:
host = 192.168.53.10 =>
表示式:
引數支援:
- 服務呼叫資訊,如:method, argument 等,暫不支援引數路由
- URL 本身的欄位,如:protocol, host, port 等
- 以及 URL 上的所有引數,如:application, organization 等
條件支援:
- 等號
=
表示"匹配",如:host = 192.168.53.10
- 不等號
!=
表示"不匹配",如:host != 192.168.53.10
值支援:
- 以逗號
,
分隔多個值,如:host != 192.168.53.10,192.168.53.11
- 以星號
*
結尾,表示通配,如:host != 10.20.*
- 以美元符
$
開頭,表示引用消費者引數,如:host = $host
-
Tips:
conditions
部分是規則的主體,由1到任意多條規則組成,下面我們就每個規則的配置語法做詳細說明:
3. 使用場景
從上面的簡單介紹我們可以大致瞭解到,當我們消費對訪問服務提供者時我們可以透過一定的規則對服務提供者列表進行過濾。那下面我們列舉下工作中常使用的場景:
- 黑名單: 比如我們需要禁止某些服務消費者消費服務
host = 192.168.53.10,192.168.53.11 =>
上面配置表示禁止192.168.53.10
、192.168.53.11
消費者訪問服務提供者。
- 服務寄宿在應用上,只暴露一部分的機器,防止整個叢集掛掉
=> host = 192.168.53.1*,192.168.53.2*
上面配置表示只能放192.168.53.1*
、192.168.53.2
ip 地址開頭的服務提供者。
- 讀寫分離:讀取資料和寫入資料操作分開
method = find*,list*,get*,is* => host = 192.168.53.10,192.168.53.11,192.168.53.12
method != find*,list*,get*,is* => host = 192.168.20.97,192.168.53.21
上面配置表示以find*,list*,get*,is*
方法命名開始的方法只能訪問192.168.53.10,192.168.53.11,192.168.53.12
服務提供者,而不是find*,list*,get*,is*
方法命名開始的方法只能訪問192.168.20.97,192.168.53.21
服務提供者。
- 提供者與消費者部署在同叢集內,本機只訪問本機的服務
=> host = $host
上面配置表示所有消費者只能訪問叢集內的服務。
4. 示例演示
我們以獲取圖書列表為例進行例項演示,其中我們會啟動兩個服務提供者配置兩個埠:20880
、20881
,然後指定路由規則為:呼叫方法queryAll
訪問20880
、呼叫方法queryByName
訪問20881
服務。專案結構圖如下:
這裡我們主要看服務提供端dubbo-provider-xml.xml
配置內容:
beans xmlns:xsi=""
xmlns:dubbo=""
xmlns=""
xsi:schemaLocation=" /spring-beans-4.3.xsd
/dubbo.xsd">
dubbo:protocol port="20880"/>
<!--分別使用 20881和20880配置啟動兩個服務-->
<!-- -->
dubbo:application name="demo-provider" metadata-type="remote"/>
dubbo:registry address="zookeeper://127.0.0.1:2181"/>
bean id="bookFacade" class="com.muke.dubbocourse.tokenverify.provider.BookFacadeImpl"/>
<!--暴露服務為Dubbo服務-->
dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" ref="bookFacade" token="12345"/>
beans>
上面我們指定了服務提供者的埠,這裡請求小夥伴分別以20880
和20881
啟動兩個服務。接下來我們看看在 Dubbo Admin 中配置的路由規則:
enabled: true
runtime: false
force: true
conditions:
- 'method = queryAll => address=*:20880'
- 'method = queryByName => address=*:20881'
如下圖所示:
**Tips:**這裡的
Service Unique ID
配置規則為:介面許可權定名:版本:分組。
5. 實現原理
根據前面的介紹我們知道在消費端呼叫遠端服務時透過路由規則進行服務的過濾,那麼我們透過原始碼簡單的分析下這個處理過程。這裡我們直接看到路由規則的呼叫核心程式碼org.apache.dubbo.rpc.cluster.RouterChain#route
核心方法如下:
public ListInvokerT>> route(URL url, Invocation invocation) {
ListInvokerT>> finalInvokers = invokers;
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
下面展示了我們執行過程中的路由規則:
其中ConditionRouter
就是我們的條件路由核心程式碼如下:
public T> ListInvokerT>> route(ListInvokerT>> invokers, URL url, Invocation invocation)
throws RpcException {
if (!enabled) {
return invokers;
}
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
//匹配 method = queryAll => address=*:20880 表示中的 method = queryAll 部分
if (!matchWhen(url, invocation)) {
return invokers;
}
ListInvokerT>> result = new ArrayListInvokerT>>();
if (thenCondition == null) {
//..
return result;
}
for (InvokerT> invoker : invokers) {
//匹配 method = queryAll => address=*:20880 表示中的 address=*:20880 部分
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
if (!result.isEmpty()) {
return result;
//配置force: true 表示如果透過路由規則後沒有服務條件的返回一個空集合,否則路由規則無效返回過濾器的 Invoker 遠端服務代理列表
} else if (force) {
//..
return result;
}
} catch (Throwable t) {
//...
}
return invokers;
}
這裡有兩個最為重要的方法分別是:org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchWhen
和org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchThen
,matchWhen
方法主要負責匹配前置條件例如:method = queryAll => address=*:20880
表示中的 method = queryAll
部分,matchThen
方法主要負責匹配後置條件method = queryAll => address=*:20880
表示中的 address=*:20880
部分。matchWhen
核心程式碼如下:
boolean matchWhen(URL url, Invocation invocation) {
//判斷whenCondition條件是否為空,並且執行matchCondition匹配表示式
return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
}
方法org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchCondition
核心程式碼如下:
/**
*
* 匹配條件
*
* @author liyong
* @date 11:36 PM 2020/11/28
* @param condition
* @param url
* @param param
* @param invocation
* @exception
* @return boolean
**/
private boolean matchCondition(MapString, MatchPair> condition, URL url, URL param, Invocation invocation) {
MapString, String> sample = url.toMap();
boolean result = false;
//method = queryAll => address=*:20880
for (Map.EntryString, MatchPair> matchPair : condition.entrySet()) {
String key = matchPair.getKey();
String sampleValue;
//從invocation中獲取呼叫的真實方法名稱
if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {
sampleValue = invocation.getMethodName();
//判斷是否配置 address
} else if (ADDRESS_KEY.equals(key)) {
sampleValue = url.getAddress();
//判斷是否配置 host
} else if (HOST_KEY.equals(key)) {
sampleValue = url.getHost();
} else {
//從URL轉換的map中獲取對應key的值
sampleValue = sample.get(key);
if (sampleValue == null) {
sampleValue = sample.get(key);
}
}
if (sampleValue != null) {
//匹配條件配置和真實呼叫引數值是否匹配
if (!matchPair.getValue().isMatch(sampleValue, param)) {
return false;
} else {
result = true;
}
} else {
//沒有匹配的條件
if (!matchPair.getValue().matches.isEmpty()) {
return false;
} else {
result = true;
}
}
}
return result;
}
上面的程式碼非常簡單小夥伴可以根據註釋去學習,其中Map
結構如下:
從資料結構中我們可以看出我們配置的method = queryByName
,我們這裡沒有配置host
預設為*
。接下來我們繼續看看matchThen
方法核心程式碼如下:
private boolean matchThen(URL url, URL param) {
//判斷thenCondition條件是否為空,並且執行matchCondition匹配表示式
return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
}
這裡和上面的matchWhen
方法都呼叫matchCondition
,那我們看看thenCondition
的資料結構:
是我們配置的規則後半部分address=*:20881
。由此我們可以總結:假設我們的條件路由規則是method = queryByName => address=*:20881
那麼先對服務呼叫方匹配method = queryByName
前部分,如果滿足前面部分則繼續去匹配規則的後面部分address=*:20881
,如果都匹配則 Invoker 代理物件將作為呼叫代理候選者。
6. 小結
在本小節中我們主要學習了 Dubbo 中路由規則之條件路由以及使用方式。同時也分析了條件路由實現的原理,其本質上是透過過濾器對服務提供者列表進行規則的匹配,如果匹配不上則過濾掉服務提供者。
本節課程的重點如下:
-
理解 Dubbo 路由規則和條件路由
-
瞭解了條件路由使用方式
-
瞭解條件路由實現原理
-
瞭解條件路由使用場景
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料儲存、自動化整合和部署、分散式微服務、響應式程式設計、人工智慧等領域。同時也熱衷於技術分享創立公眾號和部落格站點對知識體系進行分享。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2797603/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java 8 Stream Api 中的 peek 操作JavaAPI
- Stream 的幾種中間操作
- C# peek()的用法C#
- 初探Node中的stream
- Stream常用操作以及原理探索
- Java Stream流操作面試題Java面試題
- Dart中的Stream初步研究Dart
- Nodejs中的stream模組NodeJS
- java8Stream操作集錦Java
- Java中的函數語言程式設計(七)流Stream的Map-Reduce操作Java函數程式設計
- java中Stream的四種建立Java
- node.js中的流(stream)Node.js
- Java8中的Stream APIJavaAPI
- java8 Stream流操作介紹Java
- Java8之Stream常用操作方式Java
- Java8 的 Stream 流式操作之王者歸來Java
- Java8之Stream-強大的collect操作Java
- Java8的stream流讓操作集合更容易Java
- Java 8 peek() 方法的一些疑惑Java
- Java 8 中Stream用法Java
- 淺析nodejs中的stream(流)NodeJS
- java中Stream的使用流程及注意Java
- 瞭解nodeJs中的流(stream)NodeJS
- Node.js 中 Stream API 的使用Node.jsAPI
- Java Stream 流如何進行合併操作Java
- Java8中的 lambda 和Stream APIJavaAPI
- 淺談node.js中的stream(流)Node.js
- 快速認識Node.js中的StreamNode.js
- 初識Node.js中的stream(流):Node.js
- Java 8中的Stream API使用指南JavaAPI
- Java中stream流的filter機制理解JavaFilter
- Collection如何轉成stream以及Spliterator對其操作的實現
- 全面吃透JAVA Stream流操作,讓程式碼更加的優雅Java
- Flutter Stream 簡介及部分操作符使用Flutter
- Java8之Stream流(三)縮減操作Java
- Stream流的基本介紹以及在工作中的常用操作(去重、排序以及數學運算等)排序
- 設定windows 7的Aero Peek的桌面預覽功能Windows
- 【Java8新特性】面試官:談談Java8中的Stream API有哪些終止操作?Java面試API