1. 程式人生 > >Dubbo的原理以及詳細原理、配置

Dubbo的原理以及詳細原理、配置

Dubbo的背景

隨著網際網路的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分散式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。

Dubbo的應用

用於大規模服務化,通過在消費方獲取服務提供方地址列表,實現軟負載均衡,減輕硬體壓力。

架構

最簡單呼叫圖

技術分享

節點角色說明:

  • Provider: 暴露服務的服務提供方。
  • Consumer: 呼叫遠端服務的服務消費方。
  • Registry: 服務註冊與發現的註冊中心。
  • Monitor: 統計服務的呼叫次調和呼叫時間的監控中心。
  • Container: 服務執行容器。

呼叫關係說明:

  • 服務容器負責啟動,載入,執行服務提供者。
  • 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
  • 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
  • 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者。
  • 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。
  • 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心。

簡單用法

需與spring整合(也可以呼叫api,但官方不推薦,且程式碼臃腫)。Java程式碼跟正常功能一樣,只需要一個介面和一個實現類,像正常spring一樣配置。需要詳細說明的是xml配置:

提供者(服務端):

技術分享

消費者(呼叫客戶端):

技術分享

程式碼簡單解釋(配置項暫不說明):

  • 在服務端只需用<dubbo:service interface="com.ailk.uchannel.dubbo.IDemoService" ref="demoService" timeout="100000"/>這段配置來暴露服務的介面,ref的值是spring配置的bean。
  • 在客戶端<dubbo:reference id="demoService" interface="com.ailk.uchannel.dubbo.IDemoService" />,來宣告一個spring的bean,然後可以在需要的地方獲取這個bean,接著直接呼叫該介面的所有方法。如:

 技術分享

這段程式碼就是獲取spring的配置檔案後,獲取bean,然後遠端呼叫,並且獲取返回值。

 

使用協議

Dubbo協議

採用NIO複用單一長連線,並使用執行緒池併發處理請求,減少握手和加大併發效率,效能較好(推薦使用),在大檔案傳輸時,單一連線會成為瓶頸。Dubbo協議預設每服務每提供者每消費者使用單一長連線,如果資料量較大,可以使用多個連線。示例:

<dubbo:protocol name="dubbo" connections="2" />
<!--表示該服務使用JVM共享長連線(預設)-->
<dubbo:service connections=”0”>或<dubbo:reference connections=”0”>
<!--表示該服務使用獨立長連線-->
<dubbo:service connections=”1”>或<dubbo:reference connections=”1”>
<!--表示該服務使用獨立兩條長連線-->
<dubbo:service connections=”2”>或<dubbo:reference connections=”2”>
  • l 連線個數:單連線
  • l 連線方式:長連線
  • l 傳輸協議:TCP
  • l 傳輸方式:NIO非同步傳輸
  • l 序列化:Hessian二進位制序列化
  • l 適用範圍:傳入傳出引數資料包較小(建議小於100K),消費者比提供者個數多,單一消費者無法壓滿提供者,儘量不要用dubbo協議傳輸大檔案或超大字串
  • l 適用場景:常規遠端服務方法呼叫
  • l 協議約束:
  1. 引數及返回值需實現Serializable介面
  2. 引數及返回值不能自定義實現List, Map, Number, Date, Calendar等介面,只能用JDK自帶的實現,因為hessian會做特殊處理,自定義實現類中的屬性值都會丟失。
  3. 只傳成員屬性值和值的型別,不傳方法或靜態變數。
  4. 介面增加方法,對客戶端無影響,如果該方法不是客戶端需要的,客戶端不需要重新部署;輸入引數和結果集中增加屬性,對客戶端無影響,如果客戶端並不需要新屬性,不用重新部署;輸入引數和結果集屬性名變化,對客戶端序列化無影響,但是如果客戶端不重新部署,不管輸入還是輸出,屬性名變化的屬性值是獲取不到的。

 

Rmi協議

可與原生RMI互操作,基於TCP協議,偶爾會連線失敗,需重建Stub。

如果服務介面繼承了java.rmi.Remote介面,可以和原生RMI互操作,即:提供者用Dubbo的RMI協議暴露服務,消費者直接用標準RMI介面呼叫,或者提供方用標準RMI暴露服務,消費方用Dubbo的RMI協議呼叫。

如果服務介面沒有繼承java.rmi.Remote介面,預設Dubbo將自動生成一個com.xxx.XxxService$Remote的介面,並繼承java.rmi.Remote介面,並以此介面暴露服務,但如果設定了<dubbo:protocol name="rmi" codec="spring" />,將不生成$Remote介面,而使用Spring的RmiInvocationHandler介面暴露服務,和Spring相容。

  • l 連線個數:多連線
  • l 連線方式:短連線
  • l 傳輸協議:TCP
  • l 傳輸方式:同步傳輸
  • l 序列化:Java標準二進位制序列化
  • l 適用範圍:傳入傳出引數資料包大小混合,消費者與提供者個數差不多,可傳檔案
  • l 適用場景:常規遠端服務方法呼叫,與原生RMI服務互操作
  • l 協議約束:
  1. 引數及返回值需實現Serializable介面
  2. dubbo配置中的超時時間對rmi無效,需使用java啟動引數設定:-Dsun.rmi.transport.tcp.responseTimeout=3000,參見下面的RMI配置。

 

Hessian協議

可與原生Hessian互操作,基於HTTP協議,需hessian.jar支援,http短連線的開銷大。

  • l 連線個數:多連線
  • l 連線方式:短連線
  • l 傳輸協議:HTTP
  • l 傳輸方式:同步傳輸
  • l 序列化:Hessian二進位制序列化
  • l 適用範圍:傳入傳出引數資料包較大,提供者比消費者個數多,提供者壓力較大,可傳檔案
  • l 適用場景:頁面傳輸,檔案傳輸,或與原生hessian服務互操作
  • l 協議約束:
  1. 引數及返回值需實現Serializable介面.
  2. 引數及返回值不能自定義實現List, Map, Number, Date, Calendar等介面,只能用JDK自帶的實現,因為hessian會做特殊處理,自定義實現類中的屬性值都會丟失。

 

HTTP協議

基於http表單的遠端呼叫協議。

  • l 基於http表單的遠端呼叫協議。
  • l 連線個數:多連線
  • l 連線方式:短連線
  • l 傳輸協議:HTTP
  • l 傳輸方式:同步傳輸
  • l 序列化:表單序列化
  • l 適用範圍:傳入傳出引數資料包大小混合,提供者比消費者個數多,可用瀏覽器檢視,可用表單或URL傳入引數,暫不支援傳檔案。
  • l 適用場景:需同時給應用程式和瀏覽器JS使用的服務
  • l 協議約束:
  1. 引數及返回值需符合Bean規範

使用協議示例:

<dubbo:protocol name="http" port="8080" />

注意,如果使用servlet派發請求:協議的埠<dubbo:protocol port="8080" />必須與servlet容器的埠相同,協議的上下文路徑<dubbo:protocol contextpath="foo" />必須與servlet應用的上下文路徑相同。

 

Webservice協議

基於CXF的frontend-simple和transports-http實現,可以和原生WebService服務互操作,即:提供者用Dubbo的WebService協議暴露服務,消費者直接用標準WebService介面呼叫,或者提供方用標準WebService暴露服務,消費方用Dubbo的WebService協議呼叫。

  • l 連線個數:多連線
  • l 連線方式:短連線
  • l 傳輸協議:HTTP
  • l 傳輸方式:同步傳輸
  • l 序列化:SOAP文字序列化
  • l 適用場景:系統整合,跨語言呼叫
  • l 協議約束:
  1. 引數及返回值需實現Serializable介面
  2. 引數儘量使用基本型別和POJO。

 

多協議

不同服務不同協議

<!-- 多協議配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo協議暴露服務 -->
<dubbo:service interface="com.ailk.uchannel.service.interfaces.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
<!-- 使用rmi協議暴露服務 -->
<dubbo:service interface="com.ailk.uchannel.service.interfaces.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />

 

多協議暴露服務

<!-- 多協議配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多個協議暴露服務 -->
<dubbo:service id="helloService" interface="com.ailk.uchannel.service.interfaces.HelloService" version="1.0.0" protocol="dubbo,hessian" />

Dubbo註冊中心

最簡單的Multicast註冊中心

不需要註冊中心,只要廣播地址一樣,就能相互發現。只是依賴於網路拓普和路由,跨機房有風險。

組播受網路結構限制,只適合小規模應用或開發階段使用。

示例:

<!-- 使用multicast廣播註冊中心暴露服務地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />

流程說明:

  • l 提供方啟動時廣播自己的地址
  • l 消費方啟動時廣播訂閱請求
  • l 提供方收到訂閱請求時,單播自己的地址給訂閱者,如果設定了unicast=false,則廣播給訂閱者
  • l 消費方收到提供方地址時,連線該地址進行RPC呼叫。

注意:

  1. multicast地址不能配成127.0.0.1,也不能配成機器的IP地址,必須是D段廣播地址,也就是:224.0.0.0到239.255.255.255之間的任意地址。
  2. 為了減少廣播量,Dubbo預設使用單播發送提供者地址資訊給消費者,如果一個機器上同時啟了多個消費者程序,消費者需宣告unicast=false,否則只會有一個消費者能收到訊息。

 

zookeeper 推薦的註冊中心

可用叢集,官方建議不用複雜配置,也可以選擇淘寶的支援。

使用示例:

簡單:

<dubbo:registry protocol="zookeeper" address="192.168.109.130:2181"/>

叢集的zookeeper:

<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181" />

流程說明:

  • l 服務提供者啟動時,向/dubbo/com.foo.BarService/providers目錄下寫入自己的URL地址。
  • l 服務消費者啟動時,訂閱/dubbo/com.foo.BarService/providers目錄下的提供者URL地址,並向/dubbo/com.foo.BarService/consumers目錄下寫入自己的URL地址。
  • l 監控中心啟動時,訂閱/dubbo/com.foo.BarService目錄下的所有提供者和消費者URL地址。

支援以下功能:

  • l 當提供者出現斷電等異常停機時,註冊中心能自動刪除提供者資訊。
  • l 當註冊中心重啟時,能自動恢復註冊資料,以及訂閱請求。
  • l 當會話過期時,能自動恢復註冊資料,以及訂閱請求。
  • l 當設定<dubbo:registry check="false" />時,記錄失敗註冊和訂閱請求,後臺定時重試。
  • l 可通過<dubbo:registry username="admin" password="1234" />設定zookeeper登入資訊。
  • l 可通過<dubbo:registry group="dubbo" />設定zookeeper的根節點,不設定將使用無根樹。
  • l 支援*號萬用字元<dubbo:reference group="*" version="*" />,可訂閱服務的所有分組和所有版本的提供者。

zookeeper配置:

在安裝路徑下的conf目錄裡,將 zoo_sample.cfg 改名為 zoo.cfg。

  • l 單機:

  需配置項:

    tickTime=2000

    dataDir=D:/devtools/zookeeper-3.2.2/build

    clientPort=2181

  1. tickTime:這個時間是作為 Zookeeper 伺服器之間或客戶端與伺服器之間維持心跳的時間間隔,也就是每個 tickTime 時間就會發送一個心跳。
  2. dataDir:顧名思義就是 Zookeeper 儲存資料的目錄,預設情況下,Zookeeper 將寫資料的日誌檔案也儲存在這個目錄裡。
  3. clientPort:這個埠就是客戶端連線 Zookeeper 伺服器的埠,Zookeeper 會監聽這個埠,接受客戶端的訪問請求。
  • l 叢集模式:

  叢集模式除了上面的三個配置項還要增加下面幾個配置項:

  initLimit=5

  syncLimit=2

  server.1=192.168.211.1:2888:3888

  server.2=192.168.211.2:2888:3888

  1. initLimit:這個配置項是用來配置 Zookeeper 接受客戶端(這裡所說的客戶端不是使用者連線 Zookeeper 伺服器的客戶端,而是 Zookeeper 伺服器叢集中連線到 Leader 的 Follower 伺服器)初始化連線時最長能忍受多少個心跳時間間隔數。當已經超過 10 個心跳的時間(也就是 tickTime)長度後 Zookeeper 伺服器還沒有收到客戶端的返回資訊,那麼表明這個客戶端連線失敗。總的時間長度就是 5*2000=10 秒
  2. syncLimit:這個配置項標識 Leader 與 Follower 之間傳送訊息,請求和應答時間長度,最長不能超過多少個 tickTime 的時間長度,總的時間長度就是 2*2000=4 秒
  3. server.A=B:C:D:其中 A 是一個數字,表示這個是第幾號伺服器;B 是這個伺服器的 ip 地址;C 表示的是這個伺服器與叢集中的 Leader 伺服器交換資訊的埠;D 表示的是萬一叢集中的 Leader 伺服器掛了,需要一個埠來重新進行選舉,選出一個新的 Leader,而這個埠就是用來執行選舉時伺服器相互通訊的埠。如果是偽叢集的配置方式,由於 B 都是一樣,所以不同的 Zookeeper 例項通訊埠號不能一樣,所以要給它們分配不同的埠號。

  除了以上配置,在data目錄下還要放置myid檔案:(上面zoo.cfg中的dataDir),這個檔案裡面就有一個數據就是 A 的值,Zookeeper 啟動時會讀取這個檔案,拿到裡面的資料與 zoo.cfg 裡面的配置資訊比較從而判斷到底是那個 server。

 

簡易監控中心

Simple監控中心,用來監控與統計框架執行情況,掛掉不會影響到Consumer和Provider之間的呼叫,所以用於生產環境不會有風險。

管理控制檯 

依託於web容器(可以直接放到tomcat裡),開源部分主要包含:路由規則,動態配置,服務降級,訪問控制,權重調整,負載均衡,等管理功能,可以檢視提供者和消費者的一些資訊,並可以對此進行操作。

配置:

需要檢視修改/ROOT/WEB-INF/dubbo.properties檔案,配置註冊中心和登陸使用者密碼。

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest

叢集使用

特性

  • l 註冊中心通過長連線感知服務提供者的存在,服務提供者宕機,註冊中心將立即推送事件通知消費者。
  • l 註冊中心和監控中心全部宕機,不影響已執行的提供者和消費者,消費者在本地快取了提供者列表。
  • l 註冊中心對等叢集,任意一臺宕掉後,將自動切換到另一臺。
  • l 服務提供者無狀態,任意一臺宕掉後,不影響使用。
  • l 服務提供者全部宕掉後,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復。
  • l 服務提供者無狀態,可動態增加機器部署例項,註冊中心將推送新的服務提供者資訊給消費者。

容錯模式

Failover Cluster

  • l 失敗自動切換,當出現失敗,重試其它伺服器。(預設)
  • l 通常用於讀操作,但重試會帶來更長延遲。
  • l 可通過retries="2"來設定重試次數(不含第一次)。

Failfast Cluster

  • l 快速失敗,只發起一次呼叫,失敗立即報錯。
  • l 通常用於非冪等性的寫操作,比如新增記錄。

Failsafe Cluster

  • l 失敗安全,出現異常時,直接忽略。
  • l 通常用於寫入審計日誌等操作。

Failback Cluster

  • l 失敗自動恢復,後臺記錄失敗請求,定時重發。
  • l 通常用於訊息通知操作。

Forking Cluster

  • l 並行呼叫多個伺服器,只要一個成功即返回。
  • l 通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。
  • l 可通過forks="2"來設定最大並行數。

Broadcast Cluster

  • l 廣播呼叫所有提供者,逐個呼叫,任意一臺報錯則報錯。(2.1.0開始支援)
  • l 通常用於通知所有提供者更新快取或日誌等本地資源資訊。

叢集模式配置:

<dubbo:service cluster="failsafe" />或者<dubbo:reference cluster="failsafe" />

 

負載均衡

Random LoadBalance

  • l 隨機,按權重設定隨機概率。
  • l 在一個截面上碰撞的概率高,但呼叫量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。

RoundRobin LoadBalance

  • l 輪循,按公約後的權重設定輪循比率。
  • l 存在慢的提供者累積請求問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。

LeastActive LoadBalance

  • l 最少活躍呼叫數,相同活躍數的隨機,活躍數指呼叫前後計數差。
  • l 使慢的提供者收到更少請求,因為越慢的提供者的呼叫前後計數差會越大。

ConsistentHash LoadBalance

  • l 一致性Hash,相同引數的請求總是發到同一提供者。
  • l 當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
  • l 演算法參見:http://en.wikipedia.org/wiki/Consistent_hashing
  • l 預設只對第一個引數Hash,如果要修改,請配置<dubbo:parameter key="hash.arguments" value="0,1" />
  • l 預設用160份虛擬節點,如果要修改,請配置<dubbo:parameter key="hash.nodes" value="320" />

負載均衡配置:

<dubbo:service interface="..." loadbalance="roundrobin" />

<dubbo:reference interface="..." loadbalance="roundrobin" />

可以在方法上配置

<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

服務容器

服務端不需要web容器,只是一個簡單的main方法,載入spring容器,用於暴露服務。

服務容器的載入內容可以擴充套件,內建了spring, jetty, log4j等載入,可通過Container擴充套件點進行擴充套件。

  • l Spring Container

自動載入META-INF/spring目錄下的所有Spring配置。

配置:(配在java命令-D引數或者dubbo.properties中)

dubbo.spring.config=classpath*:META-INF/spring/*.xml ----配置spring配置載入位置

  • l Jetty Container

  啟動一個內嵌Jetty,用於彙報狀態。

  配置:(配在java命令-D引數或者dubbo.properties中)

dubbo.jetty.port=8080 ----配置jetty啟動埠

dubbo.jetty.directory=/foo/bar ----配置可通過jetty直接訪問的目錄,用於存放靜態檔案

dubbo.jetty.page=log,status,system ----配置顯示的頁面,預設載入所有頁面

 

  • l Log4j Container

  自動配置log4j的配置,在多程序啟動時,自動給日誌檔案按程序分目錄。

  配置:(配在java命令-D引數或者dubbo.properties中)

dubbo.log4j.file=/foo/bar.log ----配置日誌檔案路徑
dubbo.log4j.level=WARN ----配置日誌級別
dubbo.log4j.subdirectory=20880 ----配置日誌子目錄,用於多程序啟動,避免衝突

容器啟動:

如:(預設只加載spring)

java com.alibaba.dubbo.container.Main

或:(通過main函式引數傳入要載入的容器)

java com.alibaba.dubbo.container.Main spring jetty log4j

或:(通過JVM啟動引數傳入要載入的容器)

java com.alibaba.dubbo.container.Main -Ddubbo.container=spring,jetty,log4j

或:(通過classpath下的dubbo.properties配置傳入要載入的容器)

dubbo.container=spring,jetty,log4j

基本配置使用說明

Xml配置

  • <dubbo:service/> 服務配置,用於暴露一個服務,定義服務的元資訊,一個服務可以用多個協議暴露,一個服務也可以註冊到多個註冊中心。
  • <dubbo:reference/> 引用配置,用於建立一個遠端服務代理,一個引用可以指向多個註冊中心。
  • <dubbo:protocol/> 協議配置,用於配置提供服務的協議資訊,協議由提供方指定,消費方被動接受。
  • <dubbo:application/> 應用配置,用於配置當前應用資訊,不管該應用是提供者還是消費者。
  • <dubbo:module/> 模組配置,用於配置當前模組資訊,可選。
  • <dubbo:registry/> 註冊中心配置,用於配置連線註冊中心相關資訊。
  • <dubbo:monitor/> 監控中心配置,用於配置連線監控中心相關資訊,可選。
  • <dubbo:provider/> 提供方的預設值,當ProtocolConfig和ServiceConfig某屬性沒有配置時,採用此預設值,可選。
  • <dubbo:consumer/> 消費方預設配置,當ReferenceConfig某屬性沒有配置時,採用此預設值,可選。
  • <dubbo:method/> 方法配置,用於ServiceConfig和ReferenceConfig指定方法級的配置資訊。
  • <dubbo:argument/> 用於指定方法引數配置。

Properties檔案配置

Dubbo將自動載入classpath根目錄下的dubbo.properties,可以通過JVM啟動引數:-Ddubbo.properties.file=xxx.properties 改變預設配置位置。

對映規則:

  • l 將XML配置的標籤名,加屬性名,用點分隔,多個屬性拆成多行:

  比如:dubbo.application.name=foo等價於<dubbo:application name="foo" />

  比如:dubbo.registry.address=10.20.153.10:9090等價於<dubbo:registry address="10.20.153.10:9090" />

  • l 如果XML有多行同名標籤配置,可用id號區分,如果沒有id號將對所有同名標籤生效:

  比如:dubbo.protocol.rmi.port=1234等價於<dubbo:protocol id="rmi" name="rmi" port="1099" /> (協議的id沒配時,預設使用協議名作為id)

  比如:dubbo.registry.china.address=10.20.153.10:9090等價於<dubbo:registry i d="china" address="10.20.153.10:9090" />

詳細配置說明參考官方文件。

自帶優化功能

結果快取

用於加速熱門資料的訪問速度,Dubbo提供宣告式快取,以減少使用者加快取的工作量。

  • l lru 基於最近最少使用原則刪除多餘快取,保持最熱的資料被快取。
  • l threadlocal 當前執行緒快取,比如一個頁面渲染,用到很多portal,每個portal都要去查使用者資訊,通過執行緒快取,可以減少這種多餘訪問。
  • l jcache 與JSR107整合,可以橋接各種快取實現。

配置如:

<dubbo:reference interface="com.ailk.uchannel.service.interfaces.TestService" cache="lru" />

或:

<dubbo:reference interface="com.ailk.uchannel.service.interfaces.TestService">

<dubbo:method name="findBar" cache="lru" />

</dubbo:reference>

 

非同步呼叫

基於NIO的非阻塞實現並行呼叫,客戶端不需要啟動多執行緒即可完成並行呼叫多個遠端服務,相對多執行緒開銷較小。

示例:

  技術分享

程式碼呼叫:

  技術分享

你也可以設定是否等待訊息發出:(非同步總是不等待返回)

  • l sent="true" 等待訊息發出,訊息傳送失敗將丟擲異常。
  • l sent="false" 不等待訊息發出,將訊息放入IO佇列,即刻返回。

如果只是非同步呼叫,完全忽略返回值,可以配置return =”false”,j減少Future物件的建立和管理成本。

 

事件通知

在呼叫之前,呼叫之後,出現異常時,會觸發oninvoke, onreturn, onthrow三個事件,可以配置當事件發生時,通知哪個類的哪個方法。

示例:

  技術分享

注意,callback與async功能正交分解:async=true,表示結果是否馬上返回,onreturn 表示是否需要回調。

組合情況:(async=false 預設)

  • l 非同步回撥模式:async=true onreturn="xxx"
  • l 同步回撥模式:async=false onreturn="xxx"
  • l 非同步無回撥 :async=true
  • l 同步無回撥 :async=false

 

本地存根

客戶端通常只有介面,而實現全在伺服器端,但提供方有些時候想在客戶端也執行部分邏輯,比如:做ThreadLocal快取,提前驗證引數,呼叫失敗後偽造容錯資料等等,此時就需要在API中帶上Stub,客戶端生成Proxy時,會把Proxy通過建構函式傳給Stub,然後把Stub暴露給使用者,Stub可以決定要不要去調Proxy。

示例:

  技術分享

Stub程式碼

  技術分享

注意:Stub必須有可傳入Proxy的建構函式!

 

 

本地偽裝

 

是Stub的一個子集,便於服務提供方在客戶端執行容錯邏輯,因經常需要在出現RpcException(比如網路失敗,超時等)時進行容錯,而在出現業務異常(比如登入使用者名稱密碼錯誤)時不需要容錯,如果用Stub,可能就需要捕獲並依賴RpcException類,而用Mock就可以不依賴RpcException,因為它的約定就是隻有出現RpcException時才執行。

 

示例:

  技術分享

Mock程式碼:

  技術分享

如果只是想簡單的忽略異常,直接return null。

  技術分享

 

開發連調與自測

服務分組

當一個介面有多個實現時,可以用group區分。

示例:

服務端

<dubbo:service group="feedback" interface="com.ailk.uchannel.service.interfaces.IndexService" />

<dubbo:service group="member" interface="com.ailk.uchannel.service.interfaces.IndexService" />

客戶端

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.ailk.uchannel.service.interfaces.IndexService" />

<dubbo:reference id="memberIndexService" group="member" interface="com.ailk.uchannel.service.interfaces.IndexService" />

任意組:

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

 

多版本

當一個介面實現,出現不相容升級時,可以用版本號過渡,版本號不同的服務相互間不引用。

  • l 在低壓力時間段,先升級一半提供者為新版本
  • l 再將所有消費者升級為新版本
  • l 然後將剩下的一半提供者升級為新版本

示例:

<dubbo:service interface="com.ailk.uchannel.service.interfaces.TestService" version="1.0.0" />

<dubbo:reference id="testService" interface="com.ailk.uchannel.service.interfaces.TestService" version="1.0.0" />

不區分版本:

<dubbo:reference id="testService" interface="com.ailk.uchannel.service.interfaces.TestService" version="*" />

 

直連提供者

在開發及測試環境下,經常需要繞過註冊中心,只測試指定服務提供者,這時候可能需要點對點直連,點對點直聯方式,將以服務介面為單位,忽略註冊中心的提供者列表。

<dubbo:reference id="xxxService" interface="com.ailk.uchannel.service.interfaces.XxxService" url="dubbo://localhost:20890" />

 

只訂閱

為方便開發測試,經常會線上下共用一個所有服務可用的註冊中心,這時,如果一個正在開發中的服務提供者註冊,可能會影響消費者不能正常執行。

可以讓服務提供者開發方,只訂閱服務(開發的服務可能依賴其它服務),而不註冊正在開發的服務,通過直連測試正在開發的服務。

禁用註冊配置:

<dubbo:registry address="10.20.153.10:9090" register="false" />

或者

<dubbo:registry address="10.20.153.10:9090?register=false" />

泛化引用

泛介面呼叫方式主要用於客戶端沒有API介面及模型類元的情況,引數及返回值中的所有POJO均用Map表示,通常用於框架整合,比如:實現一個通用的服務測試框架,可通過GenericService呼叫所有服務實現。

示例配置:

  技術分享

Java呼叫程式碼:

  技術分享

注意:

  1. 基本型別以及Date,List,Map等不需要轉換,直接呼叫;
  2. 用Map表示POJO引數,如果返回值為POJO也將自動轉成Map;
  3. 如果是int等型別要用java.lang.Integer等替換,雙方介面都要如此。

 

獲取上下文(dubbo的配置)

上下文中存放的是當前呼叫過程中所需的環境資訊。

呼叫方法:RpcContext.getContext()。

RpcContext是一個ThreadLocal的臨時狀態記錄器,當接收到RPC請求,或發起RPC請求時,RpcContext的狀態都會變化。

延遲暴露

如果你的服務需要一定的啟動時間,比如初始化快取,等待相關資源就位等,可以使用delay進行延遲暴露。

示例:

延遲5秒暴露服務:

<dubbo:service delay="5000" />

延遲到Spring初始化完成後,再暴露服務:(基於Spring的ContextRefreshedEvent事件觸發暴露)

<dubbo:service delay="-1" />

 

Telnet命令

啟動服務後可以執行telnet命令,來檢視一些資訊(window下也可以)。

進入命令:

telnet localhost 20880。

 

幫助:help。

ls 顯示服務列表

  • l ls -l顯示服務詳細資訊列表
  • l ls XxxService顯示服務的方法列表
  • l ls -l XxxService顯示服務的方法詳細資訊列表。

Ps 顯示服務埠列表

  • l ps -l 顯示服務地址列表
  • l ps 20880 顯示埠上的連線資訊
  • l ps -l 20880 顯示埠上的連線詳細資訊。

Cd 

cd XxxService 改變預設服務,當設定了預設服務,凡是需要輸入服務名作為引數的命令,都可以省略服務引數。

cd / 取消預設服務。

Pwd 顯示當前預設服務

Trace 跟蹤方法的呼叫情況

trace XxxService 跟蹤1次服務任意方法的呼叫情況。

trace XxxService 10 跟蹤10次服務任意方法的呼叫情況。

trace XxxService xxxMethod 跟蹤1次服務方法的呼叫情況

trace XxxService xxxMethod 10 跟蹤10次服務方法的呼叫情況。

Count 統計服務的呼叫情況

count XxxService 統計1次服務任意方法的呼叫情況。

count XxxService 10 統計10次服務任意方法的呼叫情況。

count XxxService xxxMethod 統計1次服務方法的呼叫情況。

Invoke 呼叫方法

invoke XxxService.xxxMethod({"prop": "value"}) 呼叫服務的方法。

invoke xxxMethod({"prop": "value"}) 呼叫服務的方法(自動查詢包含此方法的服務)。

 

Status 顯示資源狀態

status 顯示彙總狀態,該狀態將彙總所有資源的狀態,當全部OK時則顯示OK,只要有一個ERROR則顯示ERROR,只要有一個WARN則顯示WARN。

status -l 顯示狀態列表。

  技術分享

Log 日誌

log debug 修改dubbo logger的日誌級別。

log 100 檢視file logger的最後100字元的日誌。

最佳實踐

分包

  • l 建議將服務介面,服務模型,服務異常等均放在API包中,因為服務模型及異常也是API的一部分,
  • l 同時,這樣做也符合分包原則:重用釋出等價原則(REP),共同重用原則(CRP)
  • l 如果需要,也可以考慮在API包中放置一份spring的引用配置,這樣使用方,只需在Spring載入過程中引用此配置即可,
  • l 配置建議放在模組的包目錄下,以免衝突,如:com/ailk/uchannel/dubbo-reference.xml

粒度

  • l 服務介面儘可能大粒度,每個服務方法應代表一個功能,而不是某功能的一個步驟,否則將面臨分散式事務問題,Dubbo暫未提供分散式事務支援。
  • l 服務介面建議以業務場景為單位劃分,並對相近業務做抽象,防止介面數量爆炸
  • l 不建議使用過於抽象的通用介面,如:Map query(Map),這樣的介面沒有明確語義,會給後期維護帶來不便。

版本

  • l 每個介面都應定義版本號,為後續不相容升級提供可能,如:<dubbo:service interface="com.xxx.XxxService" version="1.0" />
  • l 建議使用兩位版本號,因為第三位版本號通常表示相容升級,只有不相容時才需要變更服務版本。
  • l 當不相容時,先升級一半提供者為新版本,再將消費者全部升為新版本,然後將剩下的一半提供者升為新版本。

相容性

  • l 服務介面增加方法,或服務模型增加欄位,可向後相容,刪除方法或刪除欄位,將不相容,列舉型別新增欄位也不相容,需通過變更版本號升級。
  • l 各協議的相容性不同,參見: 服務協議

列舉值

  • l 如果是完備集,可以用Enum,比如:ENABLE, DISABLE。
  • l 如果是業務種類,以後明顯會有型別增加,不建議用Enum,可以用String代替。
  • l 如果是在返回值中用了Enum,並新增了Enum值,建議先升級服務消費方,這樣服務提供方不會返回新值。
  • l 如果是在傳入引數中用了Enum,並新增了Enum值,建議先升級服務提供方,這樣服務消費方不會傳入新值。

序列化

  • l 服務引數及返回值建議使用POJO物件,即通過set,get方法表示屬性的物件。
  • l 服務引數及返回值不建議使用介面,因為資料模型抽象的意義不大,並且序列化需要介面實現類的元資訊,並不能起到隱藏實現的意圖。
  • l 服務引數及返回值都必需是byValue的,而不能是byRef的,消費方和提供方的引數或返回值引用並不是同一個,只是值相同,Dubbo不支援引用遠端物件。

異常

  • l 建議使用異常彙報錯誤,而不是返回錯誤碼,異常資訊能攜帶更多資訊,以及語義更友好,
  • l 如果擔心效能問題,在必要時,可以通過override掉異常類的fillInStackTrace()方法為空方法,使其不拷貝棧資訊,
  • l 查詢方法不建議丟擲checked異常,否則呼叫方在查詢時將過多的try...catch,並且不能進行有效處理,
  • l 服務提供方不應將DAO或SQL等異常拋給消費方,應在服務實現中對消費方不關心的異常進行包裝,否則可能出現消費方無法反序列化相應異常。

呼叫

  • l 不要只是因為是Dubbo呼叫,而把呼叫Try-Catch起來。Try-Catch應該加上合適的回滾邊界上。
  • l 對於輸入引數的校驗邏輯在Provider端要有。如有效能上的考慮,服務實現者可以考慮在API包上加上服務Stub類來完成檢驗。

推薦用法

在Provider上儘量多配置Consumer端屬性

作服務的提供者,比服務使用方更清楚服務效能引數,如呼叫的超時時間,合理的重試次數,等等

在Provider配置後,Consumer不配置則會使用Provider的配置值,即Provider配置可以作為Consumer的預設值。

否則,Consumer會使用Consumer端的全域性設定,這對於Provider不可控的,並且往往是不合理的。

Provider上儘量多配置Consumer端的屬性,讓Provider實現者一開始就思考Provider服務特點、服務質量的問題。

在Provider可以配置的Consumer端屬性有:

  • l timeout,方法呼叫超時
  • l retries,失敗重試次數,預設是2(表示加上第一次呼叫,會呼叫3次)
  • l loadbalance,負載均衡演算法(有多個Provider時,如何挑選Provider呼叫),預設是隨機(random)。還可以有輪訓(roundrobin)、最不活躍優先(leastactive,指從Consumer端併發呼叫最好的Provider,可以減少的反應慢的Provider的呼叫,因為反應更容易累積併發的呼叫)
  • l actives,消費者端,最大併發呼叫限制,即當Consumer對一個服務的併發呼叫到上限後,新呼叫會Wait直到超時。在方法上配置(dubbo:method )則併發限制針對方法,在介面上配置(dubbo:service),則併發限制針對服務。

Provider上配置合理的Provider端屬性

Provider上可以配置的Provider端屬性有:

  • l threads,服務執行緒池大小
  • l executes,一個服務提供者並行執行請求上限,即當Provider對一個服務的併發呼叫到上限後,新呼叫會Wait(Consumer可能到超時)。在方法上配置(dubbo:method )則併發限制針對方法,在介面上配置(dubbo:service),則併發限制針對服務。

配置dubbo的快取檔案

這個檔案會快取:

  • l 註冊中心的列表
  • l 服務提供者列表

使用示例:

<dubbo:registry file=”${user.home}/output/dubbo.cache” />

有了這項配置後,當應用重啟過程中,Dubbo註冊中心不可用時則應用會從這個快取檔案讀取服務提供者列表的資訊,進一步保證應用可靠性。

Dubbo優勢

  • l 已經成熟,並且被大量專案使用。
  • l 開發簡單,同正常開發一樣,只需額外配置一段xml(dubbo支援註解方式,會更簡單,此處不討論)。
  • l 可以很容易的切入到當前工程中,無耦合!可以以一個很小的代價在當前的渠道專案中擴充套件,甚至可以直接使用(無任何修改或者很小修改)原有的介面和方法,並不需要寫太多的東西,就可以快速搭起服務平臺。

Dubbo劣勢

  • l 網上資料不是很多,遇到問題需要上官方查詢解決方案。
  • l 由於過於簡潔的開發,隱藏太多細節,如有特殊要求,恐怕改動麻煩。
  • l 基本的一些配置,依賴與優化需要投入精力去研究。

注意事項

  • l 單機可以啟動多個消費者,但因埠衝突不可以啟動多個未經更改的提供者,可在不同機器上執行,或者修改協議的埠。<dubbo:protocol name="dubbo" port="20880" />
  • l 如果使用zookeeper作為註冊中心,注意jar或者maven依賴的衝突。
  • l 如果引數在多個地方都有配置(如timeout),循序一下規則:方法級優先,介面級次之,全域性配置再次之。如果級別一樣,則消費方優先,提供方次之。
  • l 配置覆蓋的策略為:JVM啟動-D引數優先,XML次之,Properties最後。
  • l 從 dubbo 2.2.0 開始,每個服務預設都會在本地暴露;在引用服務的時候,預設優先引用本地服務;如果希望引用遠端服務可以使用一下配置強制引用遠端服務,如<dubbo:reference ... scope="remote" />.。

Maven依賴與jar包

  • l Maven依賴
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.3</version>
  </dependency>

  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.15</version>
    <exclusions>
      <exclusion>
        <groupId>com.sun.jmx</groupId>
        <artifactId>jmxri</artifactId>
      </exclusion>
      <exclusion>
        <groupId>com.sun.jdmk</groupId>
        <artifactId>jmxtools</artifactId>
      </exclusion>
      <exclusion>
        <groupId>javax.jms</groupId>
        <artifactId>jms</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

  <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.5</version>
  </dependency>

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
  </dependency>
</dependencies>
  • l Jar包

   技術分享

 

 

總結

個人感覺dubbo適合作為服務平臺改造的框架,它可以與當前渠道的工程緊密的集合,而只需要很小的代價,目前測試在資料工程測起服務,在自服務測可以正常訪問,並且返回資料庫中的結果!

如上的工作,只需要加入兩個jar包和修正一個jar包衝突(原有javassist替換成javassist-3.15.0-GA )!

如果選擇dubbo,需要的是配置的優化與選擇,以及後期測試的投入,開發的工作會小到最低,當然不包括開放新的介面。