目錄
1.Dubbo介紹
1.1工作原理
2.內建Dubbo
2.1基礎用法
1)公共接口工程
2)服務提供者
3)服務消費者
3.服務提供者和消費者的配置規則
3.1.負載均衡
3.2.叢集容錯
3.3.服務降級
3.4.重試機制
3.5.限流
1.Dubbo介紹
Apache Dubbo是一款高性能、輕量級的開源Java RPC架構,它提供了三大核心能力:面向接口的遠端方法調用,智能容錯和負載均衡,以及服務自動注冊和發現。
Dubbo就是個服務架構,如果沒有分布式的需求,其實是不需要用的,隻有在分布式的時候,才有Dubbo這樣的分布式服務架構的需求。
1.1工作原理
0:start >>>項目啟動,将服務提供者(Prvider)提供的服務加載到容器(Container).中.Container就是spring容器,這個學過spring的應該都知道。
1:register >>>将容器中的服務注冊到注冊中心(Registry)去。
2:subscribe >>>消費者(Consumer)訂閱注冊中心(Registry)。
3:notify >>> 注冊中心(Registry)告訴消費者(Consumer)我有那些服務。
4:invoke >>> 消費者(Consumer)向發起服務提供者(Prvider)調用。
5:count>>>監聽器(Monitor)會監聽所有的的執行過程。
2.內建Dubbo
2.1基礎用法
1)公共接口工程
1、建立module,名為alibaba-dubbo-common
2、建立公共接口及實體類
public interface OrderDubboService {
List<OrderInfo> findAllOrder();
OrderInfo findOrderByUserId(Long userId);
String getInfo();
}
OrderInfo.java
public class OrderInfo implements Serializable {
private Long orderId;
private Long userId;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
2)服務提供者
1、建立module,子產品名稱為alibaba-cloud-order
2、引入相關的依賴
<!--boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos注冊中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- dubbo RPC調用,注意是spring-cloud-starter-dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- 公共依賴 -->
<dependency>
<groupId>com.harvey</groupId>
<artifactId>alibaba-dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3、bootstrap.yml配置
server:
port: 8888
spring:
application:
name: alibaba-cloud-order
cloud:
nacos:
#服務注冊
discovery:
enabled: true
#nacos位址
server-addr: http://localhost:8848
#目前應用是否注冊到注冊中心,預設是true
register-enabled: true
#配置中心
config:
enabled: true
#nacos位址
server-addr: http://localhost:8848
#配合檔案字尾
file-extension: yaml
# dubbo配置
dubbo:
application:
name: ${spring.application.name}
#掃描java包,多個包用逗号分割
scan:
base-packages: com.harvey.demo
#注冊中心
registry:
#注冊中心位址清單,同一叢集内的多個位址用逗号分隔
address: nacos://127.0.0.1:8848
#是否向此注冊中心訂閱服務
subscribe: true
#注冊中心會話逾時時間(毫秒)
session: 6000
#服務提供者
provider:
register: true
#服務消費者
consumer:
#遠端服務調用重試次數,不包括第一次調用,不需要重試請設為0
retries: 2
protocol:
#随機端口,預設是20880
port: -1
name: dubbo
4、啟動類
@SpringBootApplication
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
5、Dubbo接口的實作
@DubboService
public class OrderDubboServiceImpl implements OrderDubboService {
private static Map<Long, OrderInfo> orderInfoMap = new HashMap();
static {
orderInfoMap.put(10001L, new OrderInfo(1L, 10001L));
orderInfoMap.put(10002L, new OrderInfo(2L, 10002L));
orderInfoMap.put(10003L, new OrderInfo(3L, 10003L));
}
@Override
public List<OrderInfo> findAllOrder() {
return new ArrayList(orderInfoMap.values());
}
@Override
public OrderInfo findOrderByUserId(Long userId) {
return orderInfoMap.get(userId);
}
@Override
public String getInfo() {
return "alibaba-cloud-order-01";
}
}
6、啟動項目,結果報錯:
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.<init>(ReferenceAnnotationBeanPostProcessor.java:106)
The following method did not exist:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.setClassValuesAsString(Z)V
The method's class, org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor, is available from the following locations:
jar:file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar!/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.class
The class hierarchy was loaded from the following locations:
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/org/apache/dubbo/dubbo/2.7.13/dubbo-2.7.13.jar
com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor: file:/E:/englishPath/selfrespority/com/alibaba/spring/spring-context-support/1.0.10/spring-context-support-1.0.10.jar
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter: file:/E:/englishPath/selfrespority/org/springframework/spring-beans/5.2.8.RELEASE/spring-beans-5.2.8.RELEASE.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor
一直以為是依賴沖突問題。改了無數個版本都不行,最後終于在github,spring-cloud-alibaba issues中找到了答案:更新 2.2.7 報錯 · Issue #2310 · alibaba/spring-cloud-alibaba · GitHub
單獨添加依賴:
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
重新啟動,控制台沒有報錯,檢視nacos服務清單:
實際開發中,常用namespace來隔離應用和Dubbo服務的注冊。
例如我們建立兩個命名空間dev和dubbo,應用注冊到dev,dubbo服務注冊到命名空間dubbo,修改bootstrap.yml:
spring:
cloud:
nacos:
#服務注冊
discovery:
namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297
# dubbo配置
dubbo:
#注冊中心
registry:
address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282
最終效果:
3)服務消費者
1、建立module,名為alibaba-cloud-user
2、引入相關的依賴
<!--boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos注冊中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- dubbo RPC調用,注意是spring-cloud-starter-dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
<!-- 公共依賴 -->
<dependency>
<groupId>com.harvey</groupId>
<artifactId>alibaba-dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3、bootstrap.yml
server:
port: 9999
spring:
application:
name: alibaba-cloud-user
cloud:
nacos:
#服務注冊
discovery:
enabled: true
#nacos位址
server-addr: http://localhost:8848
#目前應用是否注冊到注冊中心,預設是true
register-enabled: true
namespace: 2f7930c8-ccae-4577-bdcd-0cf874cf1297
#配置中心
config:
enabled: true
#nacos位址
server-addr: http://localhost:8848
#配合檔案字尾
file-extension: yaml
# dubbo配置
dubbo:
application:
name: ${spring.application.name}
#掃描java包,多個包用逗号分割
scan:
base-packages: com.harvey.demo
#注冊中心
registry:
#注冊中心位址清單,同一叢集内的多個位址用逗号分隔
address: nacos://127.0.0.1:8848?namespace=fab88f57-87a7-4e9b-a3a5-fcdee1605282
#是否向此注冊中心訂閱服務
subscribe: true
#注冊中心會話逾時時間(毫秒)
session: 6000
#服務提供者
provider:
register: true
#服務消費者
consumer:
#遠端服務調用重試次數,不包括第一次調用,不需要重試請設為0
retries: 2
protocol:
#随機端口,預設是20880
port: -1
name: dubbo
4、啟動類
@SpringBootApplication
public class CloudUserApp {
public static void main(String[] args) {
SpringApplication.run(CloudUserApp.class, args);
}
}
5、建立UserService、UserController完成調用
UserService.java
public interface UserService {
Object findAllOrders();
Object getOrderInfo(Long userId);
Object getInfo();
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService{
@DubboReference
private OrderDubboService orderDubboService;
@Override
public Object findAllOrders() {
return orderDubboService.findAllOrder();
}
@Override
public Object getOrderInfo(Long userId) {
return orderDubboService.findOrderByUserId(userId);
}
@Override
public Object getInfo(){
return orderDubboService.getInfo();
}
}
UserController.java
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/findAllOrders")
public Object findAllOrders() {
return userService.findAllOrders();
}
@RequestMapping("/getOrderInfo")
public Object getOrderInfo(@RequestParam("userId") Long userId) {
return userService.getOrderInfo(userId);
}
@RequestMapping("/getInfo")
public Object getInfo() {
return userService.getInfo();
}
}
6、測試驗證
啟動項目,通路 http://127.0.0.1:9999/user/findAllOrders
3.服務提供者和消費者的配置規則
dubbo推薦在Provider上盡量多配置Consumer端屬性:
作為服務的提供者,⽐服務⽅更清楚服務性能的參數,如調⽤時間,合理的重試次數等,是以這些參數應盡量配置在服務的提供者⽅;
在provider配置後,Consumer不配置則會使⽤provider的配置值,即provider的配置會作為consumer配置的預設值。如果使⽤consumer的全局配置,這對于provider是不可控的,并且是不合理的。
配置優先順序為:消費端⽅法級 > 服務端⽅法級 > 消費端接⼝級 > 服務端接⼝級 > 消費端全局 > 服務端全局
3.1.負載均衡
我們與alibaba-cloud-order一樣,建立一個新的module,alibaba-cloud-order2,将bootstrap.yml中的server.port改為7777,OrderDubboServiceImpl的getInfo方法改為:
@Override
public String getInfo() {
return "alibaba-cloud-order-02";
}
啟動項目後,我們在nacos可以看到alibaba-cloud-order有兩個執行個體:
Dubbo主要提供了以下幾種負載均衡政策:
- RandomLoadBalance (随機-random):預設政策,實際上是考慮了權重之後的随機選擇,如果每個服務提供者的權重都一緻,那麼就使用 java 的随機函數去選擇一個。
- RoundRobinLoadBalance (輪詢-roundrobin):輪詢負載均衡政策,本質上也是考慮了權重之後的輪循。如果 A 服務提供者的權重是 B 服務提供者的兩倍,那麼理論上 A 被輪循到的次數就會是 B 的兩倍。
- LeastActiveLoadBalance (最少活躍-leastactive):最少活躍調用數,相同活躍數的随機。活躍數指調用前後計數差。使慢的 Provider 收到更少請求,因為越慢的 Provider 的調用前後計數差會越大。
- ConsistentHashLoadBalance (一緻性 hash-consistenthash):使用一緻性 Hash 算法,讓相同參數的請求總是發到同一 Provider。 當某一台 Provider 崩潰時,原本發往該 Provider 的請求,基于虛拟節點,平攤到其它 Provider,不會引起劇烈變動。
- ShortestResponseLoadBalance (最短回報):根據響應時間和目前服務的請求量去獲得一個最優解。如果存在多個最優解,則考慮權重,如果僅有一個則權重無效。
1、修改服務消費者alibaba-cloud-user的配置,負載均衡政策改為輪詢
dubbo:
#服務消費者
consumer:
#負載均衡
loadbalance: roundrobin #輪詢
2、重新開機項目,通路 http://127.0.0.1:9999/user/getInfo,我們發現,alibaba-cloud-order-01 和 alibaba-cloud-order-02 是輪着展示的,說明配置成功了。
3、上面我們講到輪詢也是基于權重後的輪詢,我們在nacos上修改alibaba-cloud-order的兩個dubbo服務提供者執行個體的權重,看看效果怎樣(本人測試貌似沒起作用)。
編輯其中一個執行個體的權重,點選"确認"時報錯:
caused: errCode: 500, errMsg: do metadata operation failed ;
caused: com.alibaba.nacos.consistency.exception.ConsistencyException: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;
caused: com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException: The Raft Group [naming_instance_metadata] did not find the Leader node;
Nacos 采用 raft 算法來計算 Leader,并且會記錄前一次啟動的叢集位址,是以當我們自己的伺服器 IP 改變時(這裡特指自己學習時,在本地啟動的同學,因為有時候我們的網絡環境會變的 … WIFI,是以 IP 位址也經常變化),會導緻 raft 記錄的叢集位址失效,導緻選 Leader 出現問題,隻要删除 Nacos 根目錄下 data 檔案夾下的 protocol 檔案夾即可。
注意:該錯誤的解決方案可能不适用所有人,大家還是找下度娘,嘗試多種方案解決吧。
3.2.叢集容錯
在叢集調用失敗時,Dubbo 提供了多種容錯方案,預設為 failover 重試。
- FailoverCluster(failover):失敗自動切換,當出現失敗,重試其它伺服器。通常用于讀操作,但重試會帶來更長延遲。可通過 retries="2" 來設定重試次數(不含第一次)。
- FailfastCluster(failfast):快速失敗,隻發起一次調用,失敗立即報錯。通常用于非幂等性的寫操作,比如新增記錄。
- FailsafeCluster(failsafe):失敗安全,出現異常時,直接忽略。通常用于寫入審計日志等操作。
- FailbackCluster(failback):失敗自動恢複,背景記錄失敗請求,定時重發。通常用于消息通知操作。
- ForkingCluster(forking):并行調用多個伺服器,隻要一個成功即傳回。通常用于實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設定最大并行數。
- AvailableCluster(available):調用目前可用的執行個體(隻調用一個),如果目前沒有可用的執行個體,則抛出異常。通常用于不需要負載均衡的場景。
叢集容錯這樣配置:
dubbo:
#服務消費者
consumer:
# 叢集容錯
cluster: failover
3.3.服務降級
可以通過服務降級功能臨時屏蔽某個出錯的非關鍵服務,并定義降級後的傳回政策。
dubbo官方文檔上使用一個mock配置,實作服務降級。mock隻在出現非業務異常(比如逾時,網絡異常等)時執行。
mock的配置支援兩種:
- 配置"return null",可以很簡單的忽略掉異常。
- 為boolean值,預設的為false。如果配置為true,則預設使用mock類名,即類名+Mock字尾;
1、模拟逾時場景
1)alibaba-cloud-user配置timeout:
dubbo:
consumer:
timeout: 2000
2)alibaba-cloud-order和alibaba-cloud-order2休眠3s
@Override
public String getInfo() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "alibaba-cloud-order-01";
}
@Override
public String getInfo() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "alibaba-cloud-order-02";
}
3)重新開機項目後,通路 http://127.0.0.1:9999/user/getInfo
2、直接return null 忽略異常
服務調用方配置
dubbo:
consumer:
mock: "return null"
此時控制台還會報錯,但是頁面沒有顯示任何報錯資訊了。
3、使用mock類名,即類名+Mock字尾
在業務接口所在的包中,定義一個類,該類的命名需要滿足以下規則:業務接口簡單類名 + Mock同時實作mock接口,類名要注意命名規範:接口名+Mock字尾。此時如果調用失敗會調用Mock實作。mock實作需要保證有無參的構造方法。
1)服務調用方配置
dubbo:
consumer:
mock: true
2)服務調用方提供Mock實作(注意規範)
/**
* OrderDubboServiceMock
* 1)實作的接口是服務提供方提供的Dubbo接口OrderDubboService,不是我們的業務接口
* 2)實作類的命名規範:Dubbo接口名 + Mock
* 3)實作類與Dubbo接口必須在同一個包路徑下
* 以上條件缺一不可,否則會找不到對應的Mock類
*/
public class OrderDubboServiceMock implements OrderDubboService {
@Override
public List<OrderInfo> findAllOrder() {
List<OrderInfo> orderInfoList = new ArrayList();
orderInfoList.add(new OrderInfo(-1L, -1L));
return orderInfoList;
}
@Override
public OrderInfo findOrderByUserId(Long userId) {
return new OrderInfo(-1L, -1L);
}
@Override
public String getInfo() {
return "getInfo調用失敗了";
}
}
3)測試驗證,通路 http://127.0.0.1:9999/user/getInfo ,控制報錯了,但是浏覽器顯示如下:
說明我們的服務降級Mock生效了。
3.4.重試機制
當消費者請求⼀個服務時出現錯誤,會重試連接配接其他的伺服器,但重試會帶來更多的延遲。重試次數可以使⽤【retries=重試次數】來設定。
Dubbo的逾時重試機制為服務容錯、服務穩定提供了⽐較好的架構⽀持 dubbo在調⽤服務不成功時,預設會重試2次。Dubbo的路由機制,會把逾時的請求路由到其他機器上,⽽不是本機嘗試,是以 dubbo的重試機器也能⼀定程度的保證服務的品質。,但是在⼀些⽐較特殊的⽹絡環境下(⽹絡傳輸慢,并發多)可能由于服務響應慢,Dubbo⾃⾝的逾時重試機制(服務端的處理時間超過了設定的逾時時間時,就會有重複請求)可能會帶來⼀些⿇煩。
常見的應⽤場景故障:
- 發送郵件(重複) ;
- 賬戶注冊(重複)
解決方案:
1)對于核⼼的服務中⼼,去除dubbo逾時重試機制,并重新評估設定逾時時間。
retries設定為0,表示不重試。
2)業務處理代碼必須放在服務端,用戶端隻做參數驗證和服務調⽤,不涉及業務流程處理。
3.5.限流
為了防止某個消費者的QPS或是所有消費者的QPS總和突然飙升而導緻的重要服務的失效,系統可以對通路流量進行控制,這種對叢集的保護措施稱為服務限流。
Dubbo中能夠實作服務限流的方式較多,可以劃分為兩類:直接限流與間接限流
- 直接限流:通過對連接配接數量直接進行限制來達到限流的目的。
- 間接限流:通過一些非連接配接數量設定來達到限制流量的目的。
1、executes直接限流– 僅提供者端
該屬性僅能設定在提供者端。可以設定為接口級别,也可以設定為方法級别。限制的是服務(方法)并發執行數量。
服務提供者:
dubbo:
provider:
executes: 10
接口級别:
@DubboService(executes = 10)
方法級别:
@DubboService(methods = {
@Method(name = "getInfo", executes = 10)
})
2、accepts限流 – 僅提供者端
用于對指定協定的連接配接數量進行限制。
3、actives限流 – 兩端
該限流方式與前兩種不同的是,其可以設定在提供者端,也可以設定在消費者端。可以設定為接口級别,也可以設定為方法級别
1)提供者端限流
根據消費者與提供者間建立的連接配接類型的不同,其意義也不同。
- 長連接配接:表示目前長連接配接最多可以處理的請求個數。與長連接配接的數量沒有關系
- 短連接配接:表示目前服務可以同時處理的短連接配接數量
2)消費者端限流
根據消費者與提供者間建立的連接配接類型的不同,其意義也不同:
- 長連接配接:表示目前消費者所發出的長連接配接中最多可以送出的請求個數。與長連接配接的數量沒有關系。
- 短連接配接:表示目前消費者可以送出的短連接配接數量
4、connections限流
可以設定在提供者端,也可以設定在消費者端。限定連接配接的個數。對于短連接配接,該屬性效果與actives相同。但對于長連接配接,其限制的是長連接配接的個數。
一般情況下,會使connectons與actives聯用,讓connections限制長連接配接個數,讓actives限制一個長連接配接中可以處理的請求個數。聯用前提:使用預設的Dubbo服務暴露協定。
5、間接限流
1)延遲連接配接 – 僅消費者端
僅可設定在消費者端,且不能設定為方法級别。僅作用于Dubbo服務暴露協定。
将長連接配接的建立推遲到消費者真正調用提供者時。
可以減少長連接配接的數量。
@DubboReference(lazy = true)
2)粘連連接配接 – 僅消費者
僅能設定在消費者端,其可以設定為接口級别,也可以設定為方法級别。僅作用于Dubbo服務暴露協定。
其會使用戶端盡量向同一個提供者發起調用,除非該提供者挂了,其會連接配接另一台。隻要啟用了粘連連接配接,其就會自動啟用延遲連接配接。
其限制的是流向,而非流量。
@DubboReference(sticky = true)
3)負載均衡
可以設定在消費者端,亦可設定在提供者端;可以設定在接口級别,亦可設定在方法級别。其限制的是流向,而非流量。
- 本文作者: 百裡淺暮
- 本文連結: https://www.cnblogs.com/xfeiyun/p/16226013.html