dubbo
- dubbo連接配接方式
-
- 采用zookeeper作為注冊中心
-
- 使用場景:
- 配置:
- 無注冊中心, 直連接配接方式
-
- 使用場景:
- 配置:
- 分組連接配接
-
- 使用場景:
- 配置:
- dubbo連接配接所使用的協定
-
- dubbo://協定
-
- 使用場景:
- 特性:
- rmi://協定
-
- 使用場景:
- 特性
- hessian://協定
-
- 使用場景:
- 特性
- http://協定
-
- 使用場景:
- 特性
- webservice://協定
-
- 使用場景:
- 特性
- thrift://協定
-
- 使用場景:
- memcached://協定
-
- 注冊 memcached 服務的位址
- 在用戶端引用
- redis://協定
-
- 注冊 redis 服務的位址
- 在用戶端引用
- 雪崩效應
-
- 1 服務雪崩的原因
-
- a. 某幾個機器故障:
- b. 伺服器負載發生變化:
- c. 人為因素:
- 2 解決或緩解服務雪崩的方案
-
- a. 熔斷模式:
- b. 隔離模式:
- c. 限流模式:
- 3 熔斷設計
-
- a. 熔斷請求判斷機制算法:
- b. 熔斷恢複:
- c. 熔斷報警:
- 4 隔離設計
-
- a. 線程池隔離模式:
- b. 信号量隔離模式:
- 5 逾時機制設計
- 分布式接口的幂等設計
-
- 什麼是接口的幂等性
-
- 解釋
- 舉例
- 解決方案
-
- 全局唯一ID(通用解決方案)
- 去重表(适用于插入或更新操作)
- 插入或更新(适用于插入或更新)
-
- 多版本控制(适用于更新)
- 狀态機控制(适用于某狀态字段有序更新)
dubbo連接配接方式
采用zookeeper作為注冊中心
使用場景:
線上上部署階段使用, 對于某些并發通路壓力大的伺服器節點可以部署叢集, 這時dubbo的服務提供方伺服器叢集可以使用zookeeper來管理.
配置:
服務提供方配置:
<!-- 聲明應用名稱 -->
<dubbo:application name="pinyougou-sellergoods-service"/>
<!--注冊中心為zookeeper -->
<dubbo:registry protocol="zookeeper" address="192.168.200.128:2181"/>
<!-- 暴露20881端口, 服務調用方通過這個端口調用本機service服務 -->
<dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
<!-- 包掃描, 在這個包下面編寫service實作 -->
<dubbo:annotation package="cn.itcast.core.service" />
服務調用方配置:
<!-- 聲明應用名稱 -->
<dubbo:application name="pinyougou-manager-web" />
<!--注冊中心為zookeeper -->
<dubbo:registry address="zookeeper://192.168.200.128:2181"/>
<!-- 配置包掃描, 在此包下調用dubbo服務提供方 -->
<dubbo:annotation package="cn.itcast.core.controller" />
無注冊中心, 直連接配接方式
使用場景:
在開發階段, 服務提供方沒有必要部署叢集, 是以采用服務調用方直接連接配接服務提供方更友善測試與開發.
配置:
服務提供方配置:
<!-- 1. 聲明應用名稱 -->
<dubbo:application name="pinyougou-sellergoods-service"/>
<!-- 2. 配置直接連接配接 -->
<dubbo:registry address="N/A"/>
<!-- 3. 标記自己的ip和端口 -->
<dubbo:protocol port="20880" host="127.0.0.1"/>
<!-- 4. 提供方注冊接口 -->
<dubbo:service interface="cn.itcast.core.service.TbTestService" ref="tbTestServiceImpl"/>
服務調用方配置
<!-- 1. 聲明應用名稱 -->
<dubbo:application name="pinyougou-manager-web"/>
<!-- 2. 配置直接連接配接 -->
<dubbo:registry address="N/A"/>
<!-- 3. 消費方調用服務配置 -->
<dubbo:reference interface="cn.itcast.core.service.TbTestService" id="tbTestServiceImpl"
url="dubbo://127.0.0.1:20880"
/>
分組連接配接
使用場景:
當一個接口有多種實作時,可以用 group 區分。
配置:
服務提供方:
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
服務調用方指定調用的分組:
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />
服務調用方調用任意分組:
注意: 分組配置需要dubbo
2.2.0
以上版本支援.
dubbo連接配接所使用的協定
dubbo://協定
使用場景:
Dubbo 預設協定采用單一長連接配接和 NIO 異步通訊,适合于小資料量大并發的服務調用,以及服務消費者機器數遠大于服務提供者機器數的情況。
反之,Dubbo 預設協定不适合傳送大資料量的服務,比如傳檔案,傳視訊等,除非請求量很低。
特性:
預設協定,使用基于 minai a
1.1.7
和 hessian
3.2.1
的 tbremoting 互動。
- 連接配接個數:單連接配接
- 連接配接方式:長連接配接
- 傳輸協定:TCP
- 傳輸方式:NIO 異步傳輸
- 序列化:Hessian 二進制序列化
- 适用範圍:傳入傳出參數資料包較小(建議小于100K),消費者比提供者個數多,單一消費者無法壓滿提供者,盡量不要用 dubbo 協定傳輸大檔案或超大字元串。
- 适用場景:正常遠端服務方法調用
rmi://協定
使用場景:
RMI 協定采用 JDK 标準的
java.rmi.*
實作,采用阻塞式短連接配接和 JDK 标準序列化方式。
注意:如果正在使用 RMI 提供服務給外部通路 ,同時應用裡依賴了老的 common-collections 包的情況下,存在反序列化安全風險 。
特性
- 連接配接個數:多連接配接
- 連接配接方式:短連接配接
- 傳輸協定:TCP
- 傳輸方式:同步傳輸
- 序列化:Java 标準二進制序列化
- 适用範圍:傳入傳出參數資料包大小混合,消費者與提供者個數差不多,可傳檔案。
- 适用場景:正常遠端服務方法調用,與原生RMI服務互操作
hessian://協定
使用場景:
Hessian協定用于內建 Hessian 的服務,Hessian 底層采用 Http 通訊,采用 Servlet 暴露服務,Dubbo 預設内嵌 Jetty 作為伺服器實作。
Dubbo 的 Hessian 協定可以和原生 Hessian 服務互操作,即:
- 提供者用 Dubbo 的 Hessian 協定暴露服務,消費者直接用标準 Hessian 接口調用
- 或者提供方用标準 Hessian 暴露服務,消費方用 Dubbo 的 Hessian 協定調用。
特性
- 連接配接個數:多連接配接
- 連接配接方式:短連接配接
- 傳輸協定:HTTP
- 傳輸方式:同步傳輸
- 序列化:Hessian二進制序列化
- 适用範圍:傳入傳出參數資料包較大,提供者比消費者個數多,提供者壓力較大,可傳檔案。
- 适用場景:頁面傳輸,檔案傳輸,或與原生hessian服務互操作
http://協定
使用場景:
基于 HTTP 表單的遠端調用協定,采用 Spring 的 HttpInvoker 實作
特性
- 連接配接個數:多連接配接
- 連接配接方式:短連接配接
- 傳輸協定:HTTP
- 傳輸方式:同步傳輸
- 序列化:表單序列化
- 适用範圍:傳入傳出參數資料包大小混合,提供者比消費者個數多,可用浏覽器檢視,可用表單或URL傳入參數,暫不支援傳檔案。
- 适用場景:需同時給應用程式和浏覽器 JS 使用的服務。
webservice://協定
使用場景:
基于 WebService 的遠端調用協定,基于 Apache CXF 的
frontend-simple
和
transports-http
實作 。可以和原生 WebService 服務互操作,即:
- 提供者用 Dubbo 的 WebService 協定暴露服務,消費者直接用标準 WebService 接口調用,
- 或者提供方用标準 WebService 暴露服務,消費方用 Dubbo 的 WebService 協定調用。
特性
- 連接配接個數:多連接配接
- 連接配接方式:短連接配接
- 傳輸協定:HTTP
- 傳輸方式:同步傳輸
- 序列化:SOAP 文本序列化
- 适用場景:系統內建,跨語言調用
thrift://協定
使用場景:
目前 dubbo 支援的 thrift 協定是對 thrift 原生協定的擴充,在原生協定的基礎上添加了一些額外的頭資訊,比如 service name,magic number 等。
使用 dubbo thrift 協定同樣需要使用 thrift 的 idl compiler 編譯生成相應的 java 代碼,後續版本中會在這方面做一些增強。
memcached://協定
基于 memcached實作的 RPC 協定。
注冊 memcached 服務的位址
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("memcached://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));
在用戶端引用
在用戶端使用:
或者,點對點直連:
也可以使用自定義接口:
方法名建議和 memcached 的标準方法名相同,即:get(key), set(key, value), delete(key)。
如果方法名和 memcached 的标準方法名不相同,則需要配置映射關系:
redis://協定
基于 Redis實作的 RPC 協定。
注冊 redis 服務的位址
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("redis://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));
在用戶端引用
在用戶端使用:
或者,點對點直連:
也可以使用自定義接口:
方法名建議和 redis 的标準方法名相同,即:get(key), set(key, value), delet(key)。
如果方法名和 redis 的标準方法名不相同,則需要配置映射關系:
雪崩效應
1 服務雪崩的原因
a. 某幾個機器故障:
例如機器的硬驅動引起的錯誤,或者一些特定的機器上出現一些的bug(如,記憶體中斷或者死鎖)。
b. 伺服器負載發生變化:
某些時候服務會因為使用者行為造成請求無法及時處理進而導緻雪崩,例如阿裡的雙十一活動,若沒有提前增加機器預估流量則會造伺服器壓力會驟然增大二挂掉。
c. 人為因素:
比如代碼中的路徑在某個時候出現bug
2 解決或緩解服務雪崩的方案
一般情況對于服務依賴的保護主要有3中解決方案:
a. 熔斷模式:
這種模式主要是參考電路熔斷,如果一條線路電壓過高,保險絲會熔斷,防止火災。放到我們的系統中,如果某個目标服務調用慢或者有大量逾時,此時,熔斷該服務的調用,對于後續調用請求,不在繼續調用目标服務,直接傳回,快速釋放資源。如果目标服務情況好轉則恢複調用。
b. 隔離模式:
這種模式就像對系統請求按類型劃分成一個個小島的一樣,當某個小島被火少光了,不會影響到其他的小島。例如可以對不同類型的請求使用線程池來資源隔離,每種類型的請求互不影響,如果一種類型的請求線程資源耗盡,則對後續的該類型請求直接傳回,不再調用後續資源。這種模式使用場景非常多,例如将一個服務拆開,對于重要的服務使用單獨伺服器來部署,再或者公司最近推廣的多中心。
c. 限流模式:
上述的熔斷模式和隔離模式都屬于出錯後的容錯處理機制,而限流模式則可以稱為預防模式。限流模式主要是提前對各個類型的請求設定最高的QPS門檻值,若高于設定的門檻值則對該請求直接傳回,不再調用後續資源。這種模式不能解決服務依賴的問題,隻能解決系統整體資源配置設定問題,因為沒有被限流的請求依然有可能造成雪崩效應。
3 熔斷設計
在熔斷的設計主要參考了hystrix的做法。其中最重要的是三個子產品:熔斷請求判斷算法、熔斷恢複機制、熔斷報警
a. 熔斷請求判斷機制算法:
使用無鎖循環隊列計數,每個熔斷器預設維護10個bucket,每1秒一個bucket,每個blucket記錄請求的成功、失敗、逾時、拒絕的狀态,預設錯誤超過50%且10秒内超過20個請求進行中斷攔截。
b. 熔斷恢複:
對于被熔斷的請求,每隔5s允許部分請求通過,若請求都是健康的(RT<250ms)則對請求健康恢複。
c. 熔斷報警:
對于熔斷的請求打日志,異常請求超過某些設定則報警
4 隔離設計
隔離的方式一般使用兩種
a. 線程池隔離模式:
使用一個線程池來存儲目前的請求,線程池對請求作處理,設定任務傳回處理逾時時間,堆積的請求堆積入線程池隊列。這種方式需要為每個依賴的服務申請線程池,有一定的資源消耗,好處是可以應對突發流量(流量洪峰來臨時,處理不完可将資料存儲到線程池隊裡慢慢處理)
b. 信号量隔離模式:
使用一個原子計數器(或信号量)來記錄目前有多少個線程在運作,請求來先判斷計數器的數值,若超過設定的最大線程個數則丢棄新請求,若不超過則執行計數操作請求來計數器+1,請求傳回計數器-1。這種方式是嚴格的控制線程且立即傳回模式,無法應對突發流量(流量洪峰來臨時,處理的線程超過數量,其他的請求會直接傳回,不繼續去請求依賴的服務)
5 逾時機制設計
逾時分兩種,一種是請求的等待逾時,一種是請求運作逾時。
等待逾時:在任務入隊列時設定任務入隊列時間,并判斷隊頭的任務入隊列時間是否大于逾時時間,超過則丢棄任務。
運作逾時:直接可使用線程池提供的get方法
分布式接口的幂等設計
什麼是接口的幂等性
解釋
什麼是接口的幂等性,接口的幂等性實際上就是接口可重複調用,在調用方多次調用的情況下,接口最終得到的結果是一緻的。有些接口可以天然的實作幂等性,比如查詢接口,對于查詢來說,你查詢一次和兩次,對于系統來說,沒有任何影響,查出的結果也是一樣。
除了查詢功能具有天然的幂等性之外,增加、更新、删除都要保證幂等性。那麼如何來保證幂等性呢?
舉例
在微服務架構下,我們在完成一個訂單流程時經常遇到下面的場景:
- (重複建立)一個訂單建立接口,第一次調用逾時了,然後調用方重試了一次
- (重複更新)在訂單建立時,我們需要去扣減庫存,這時接口發生了逾時,調用方重試了一次
- (重複更新)當這筆訂單開始支付,在支付請求發出之後,在服務端發生了扣錢操作,接口響應逾時了,調用方重試了一次
- (無序更新)一個訂單狀态更新接口,調用方連續發送了兩個消息,一個是已建立,一個是已付款。但是你先接收到已付款,然後又接收到了已建立
以上問題,就是在單體架構轉成微服務架構之後,帶來的問題。當然不是說單體架構下沒有這些問題,在單體架構下同樣要避免重複請求。但是出現的問題要比這少得多。
解決方案
全局唯一ID(通用解決方案)
如果使用全局唯一ID,就是根據業務的操作和内容生成一個全局ID,在執行操作前先根據這個全局唯一ID是否存在,來判斷這個操作是否已經執行。如果不存在則把全局ID,存儲到存儲系統中,比如資料庫、redis等。如果存在則表示該方法已經執行。
從工程的角度來說,使用全局ID做幂等可以作為一個業務的基礎的微服務存在,在很多的微服務中都會用到這樣的服務,在每個微服務中都完成這樣的功能,會存在工作量重複。另外打造一個高可靠的幂等服務還需要考慮很多問題,比如一台機器雖然把全局ID先寫入了存儲,但是在寫入之後挂了,這就需要引入全局ID的逾時機制。
使用全局唯一ID是一個通用方案,可以支援插入、更新、删除業務操作。但是這個方案看起來很美但是實作起來比較麻煩,下面的方案适用于特定的場景,但是實作起來比較簡單。
去重表(适用于插入或更新操作)
這種方法适用于在業務中有唯一辨別的插入場景中,比如在以上的支付場景中,如果一個訂單隻會支付一次,是以訂單ID可以作為唯一辨別。這時,我們就可以建一張去重表,并且把唯一辨別作為唯一索引,在我們實作時,把建立支付單據和寫入到去重表,放在一個事務中,如果重複建立,資料庫會抛出唯一限制異常,操作就會復原。
插入或更新(适用于插入或更新)
這種方法插入并且有唯一索引的情況,比如我們要關聯商品品類,其中商品的ID和品類的ID可以構成唯一索引,并且在資料表中也增加了唯一索引。這時就可以使用InsertOrUpdate操作。在mysql資料庫中如下:
insert into goods_category (goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE
update_time=now()
多版本控制(适用于更新)
這種方法适合在更新的場景中,比如我們要更新商品的名字,這時我們就可以在更新的接口中增加一個版本号,來做幂等
在實作時可以如下
狀态機控制(适用于某狀态字段有序更新)
這種方法适合在有狀态機流轉的情況下,比如就會訂單的建立和付款,訂單的建立肯定是在之前,這時我們可以通過在設計狀态字段時,使用int類型,并且通過值類型的大小來做幂等,比如訂單的建立為0,付款成功為100。付款失敗為99
在做狀态機更新時,我們就這可以這樣控制
以上就是保證接口幂等性的一些方法。