1. dubbo 有什麼用
在分佈式系統中,服務與服務之間怎麼通信是一個問題,目前主流的方式就是通過RPC或HTTP協議進行通信。像Spring Cloud就是通過http協議進行服務之間的通信,而dubbo是一個RPC框架,它實現了RPC調用。這兩種方式對比起來的話,HTTP協議稍微簡單點,但是由於它需要3次握手和4次揮手,性能較差,而dubbo實現的RPC,底層是用netty這種非阻塞I/O,速度會快很多。
性能:
- http: 由於它是無狀態的,每次調用它都需要3次握手和4次揮手,性能較差。
- dubbo: 底層是用netty使種非阻塞I/O,性能較好。
編程過程中的使用方式:
- http: 兩個系統的代碼沒有任何交集,只需要一個提供服務,一個調用服務,通過json串來傳輸數據
- dubbo:由於RPC的調用就像調用本地方法一樣調用遠程方法。那在分佈式系統中,各個系統的文件都是獨享的,那我怎麼知道你把我需要的服務寫在哪個方法了?此時,就需要一箇中間媒介,這個媒介就是存放接口的一個項目,該項目中只放一些公共的接口和domain,然後服務提供方和服務消費方都去依賴這個項目,並且服務提供方去實現方法,並把方法暴露出來,服務消費方,就可以引入接口,然後調用方法。由此來完成服務的調用
那除了解決分佈式系統之間的通信問題,dubbo還提供了一些其他的功能,:
服務自動註冊與發現,服務熔斷,服務降級,軟負載均衡等,功能還在持續更新
由於Spring Cloud 是一個分佈式一站式的解決方案,且代碼都是通過上層接口去實現各個組件,所以可以直接在spring cloud中使用dubbo作爲RPC通信框架,來解決Spring Cloud使用http協議的性能問題。
Spring Cloud Alibaba 集成了Alibaba的一些開源組件,使得Spring Cloud中的組件選擇更加豐富。
2. dubbo架構
官網上有:具體說明官網上有
- 服務容器負責啓動,加載,運行服務提供者。
- 服務提供者在啓動時,向註冊中心註冊自己提供的服務。
- 服務消費者在啓動時,向註冊中心訂閱自己所需的服務。
- 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
- 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
- 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。
3. 特點
連通性、健壯性、伸縮性、以及向未來架構的升級性。官方文檔都有,我自己歸納整理了一下:
連通性
- 服務提供者和消費者只在啓動時與註冊中心交互,服務提供者註冊服務到註冊中心,服務消費者向註冊中心請求獲取服務清單
- 之後的請求,消費者直接通過初始時獲取的清單,並使用負載均衡來決定訪問哪一臺服務器
- 註冊中心,服務提供者,服務消費者三者之間均爲長連接,當註冊中心感知到有服務的上下線,那麼會立即推送事件通知消費者,然後消費者更新本地服務清單
- 註冊中心和監控中心全部宕機,不影響已運行的提供者和消費者,消費者在本地緩存了提供者列表
- 註冊中心和監控中心都是可選的,當不使用註冊中心,服務消費者可以直連服務提供者,當然這樣寫代碼就比較麻煩了
健壯性
- zk的數據庫宕掉後,註冊中心仍能通過緩存提供服務列表查詢,但不能註冊新服務
- 註冊中心在集羣模式下,任意一臺宕掉後,都不影響使用,但是宕機的機器不能超過集羣總數的一半以上,否則zk就無法使用了。且如果宕掉的是leader,那麼需要重新選擇leader,該過程比較耗時,且該過程中,服務無法使用
- 註冊中心全部宕掉後,服務提供者和服務消費者仍能通過本地緩存通訊
- 服務提供者宕機對zk沒有任何影響,只是需要通知一下消費者,如果服務提供者全部宕機,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復
伸縮性
- 註冊中心爲對等集羣,可動態增加機器部署實例,所有客戶端將自動發現新的註冊中心
- 服務提供者無狀態,可動態增加機器部署實例,註冊中心將推送新的服務提供者信息給消費者
4. 使用springboot 集成dubbo
集成dubbo的方式有很多種,我們這裏就用springboot來集成。
需要創建3個項目,
- gmall-interface:提供公共接口和公共pojo
- gmall-user: 服務提供者,提供一個查詢用戶列表的服務
- gmall-order:服務消費者,消費用戶服務
dubbo-spring-boot-starter 依賴有兩個,一個是:
另一個是
看更新時間就能知道,apache.dubbo是最新的,且之後的dubbo-spring-boot-starter都是交給apache的。我們這裏就使用apache.dubbo。
dubbo: https://github.com/apache/dubbo
dubbo-spring-boot地址:https://github.com/apache/dubbo-spring-boot-project
我的spring boot 版本是2.2.6.RELEASE,
dubbo-spring-boot-starter的版本是2.7.3
4.1 gmall-interface
該項目只存放公共的資源,所以不需要使用spring boot,創建一個普通的maven工程即可
結構如下:
UserService:
public interface UserService {
List<User> getUserList();
}
4.2 gmall-user
創建一個springboot項目
引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入公共模塊-->
<dependency>
<groupId>com.tanfp.dubbo-study.dubbo-demo</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
</dependencies>
必須要引入curator-framework,curator-recipes這兩個包,當你使用zookeeper作爲註冊中心時,它就會去者兩個包中找一些類來操作zookeeper。
netty-all這個包當你遇到io/netty的問題再加吧,我這裏其實不加也是可以正常訪問的
結構如下,裏面放gmall-interface項目中接口的實現類
UserserviceImpl.class
// 注意引入包時,引入dubbo的Service包
@Service(version = "1.0")
public class UserServiceImpl implements UserService {
@Override
public List<User> getUserList() {
List<User> userList = new ArrayList<>();
User user = new User("張三z",1,new Date());
User user2 = new User("李四",2,new Date());
userList.add(user);
userList.add(user2);
return userList;
}
}
@Service表示對外暴露了這個類,即暴露了服務,可以讓消費者來直接在本地調用UserserviceImpl類中的方法
添加註解
啓動類上添加@EnableDubbo註解
修改application.yml
server.port=8082
#指定當前dubbo引用的名稱
dubbo.application.name=gmall-user
#指定使用什麼註冊中心
dubbo.registry.protocol=zookeeper
#指定註冊中心的地址
dubbo.registry.address=127.0.0.1:2181
#指定使用什麼通信協議
dubbo.protocol.name=dubbo
4.3 gmall-order
創建一個spring boot項目,依賴與gmall-user項目一樣
結構如下
OrderController:
@RestController
public class OrderController {
@Autowired
private OrderServiceImpl orderService;
@GetMapping("/users")
public List<User> getUserList() {
return orderService.getUserList();
}
}
OrderServiceImpl:
@Service
public class OrderServiceImpl {
// 調用dubbo服務
@Reference(version = "1.0")
private UserService userService;
public List<User> getUserList() {
return userService.getUserList();
}
}
啓動類添加註解
啓動類添加@EnableDubbo註解
修改application.yml配置
server.port=8081
dubbo.application.name=gmall-order-web
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
dubbo.protocol.name=dubbo
4.4 啓動訪問
啓動zookeeper集羣,然後分別啓動gmall-user(服務提供者),gmall-order(服務消費者)
訪問http://localhost:8081/users
5. 具體配置
dubbo中具體的配置項,在官網中都有
除了dubbo:service和dubbo:reference,其他的基本上都有,這兩個分別使用@Service,@Reference去配置
對應application.yml中就是:
dubbo.protocol.xxx=yyy
dubbo.registry.xxx=yyy
xxx表示具體的屬性,yyy表示值
6. 主要功能
6.1 服務降級
當服務器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務器資源以保證核心交易正常運作或高效運作。
可以通過服務降級功能臨時屏蔽某個出錯的非關鍵服務,並定義降級後的返回策略。
一般都是在消費者的reference屬性上添加mock值來判斷,也只能消費方纔能加,提供方加這個mock會報錯。其實想想也能知道,它有一種是不進行調用,直接返回錯誤,如果放在提供服務方,還怎麼直接返回?
mock的寫法有以下幾種:
- return empty: 代表空,基本類型的默認值,或者集合類的空值
- return null
- return true
- return false
- return JSON 格式: 反序列化 JSON 所得到的對象
- fail:return null 表示會進行遠程調用,如果調用失敗,則返回null
- force:return null,直接不進行遠程調用了,方法直接返回null
- throw 直接拋出異常對象
- 自定義返回類
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000)
@Reference(version = "1.0",mock = "force:return null",timeout = 2000)
private UserService userService;
自定義返回:
自定義返回的類放在公共接口的項目:gmall-interface下,有兩點注意:
- 注意類的名稱是接口名+Mock
- 要放在與UserService接口相同的包下
UserServiceMock:
public class UserServiceMock implements UserService {
public List<User> getUserList() {
System.out.println("調用了Mock");
return new ArrayList<User>();
}
}
消費方啓用mock功能:
@Reference(version = "1.0",mock = "true",timeout = 2000)
private UserService userService;
6.2 服務熔斷(集羣容錯)
在集羣調用失敗時,Dubbo 提供了多種容錯方案,缺省爲 failover 重試。
一般來說,服務提供方和服務消費方都能設置,但是能在服務提供方進行配置的就儘量在服務提供方設置,可以更好的管理服務
Failover Cluster
失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。可通過 retries="2" 來設置重試次數(不含第一次)。
在服務提供方添加重試次數。不設置cluster,則默認是Failover重試機制
@Service(version = "1.0",retries = 2, cluster="failsafe")
Failfast Cluster
快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
非冪等性表示多次調用結果都是一樣的,比如查詢,刪除,更新
Failsafe Cluster
失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。
Failback Cluster
失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。
Forking Cluster
並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。
Broadcast Cluster
廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息。
6.3 負載均衡
默認採用隨機算法
Random LoadBalance
- 隨機,按權重設置隨機概率。
- 在一個截面上碰撞的概率高,但調用量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。
RoundRobin LoadBalance
- 輪詢,按公約後的權重設置輪詢比率。
- 存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。
LeastActive LoadBalance
- 最少活躍調用數,相同活躍數的隨機,活躍數指調用前後計數差。
- 使慢的提供者收到更少請求,因爲越慢的提供者的調用前後計數差會越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同參數的請求總是發到同一提供者。
使用:
@Service(version = "1.0",retries = 2, cluster="failsafe", loadbalance="roundrobin")
6.4 服務分組
當一個接口有多種實現時,可以用 group 區分。
@Service(version = "1.0",retries = 2, group = "impl-1")
public class UserServiceImpl implements UserService {}
@Service(version = "1.0",retries = 2, group = "impl-2")
public class UserServiceImpl implements UserService {}
服務消費方引用的時候也加一個group屬性執行即可,也可以使用*:表示使用任意一個實現類
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "impl-1")
// 使用任意組
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "*")
6.5 多版本
當一個接口實現,出現不兼容升級時,可以用版本號過渡,版本號不同的服務相互間不引用。
可以按照以下的步驟進行版本遷移:
- 在低壓力時間段,先升級一半提供者爲新版本
- 再將所有消費者升級爲新版本
- 然後將剩下的一半提供者升級爲新版本
使用方式就是指定version版本號,服務消費者只能消費指定版本的服務
@Service(version = "2.0.0")
@Reference(version = "2.0.0")
如果消費方不區分服務版本號,那麼消費方可以使用*
如果不需要區分版本,可以按照以下的方式配置 :
@Reference( version="*")
6.6 本地存根
當服務消費方想在調用服務提供方前,去做一些事情,比如說註冊,服務消費方可以先執行一部分邏輯,比如參數的校驗,只有校驗成功,我們再去調用服務提供方的註冊方法,否則就直接返回錯誤。那這個具體怎麼實現呢?
分兩步:
- 先在gmall-interface項目的service包下創建一個UserServiceStub類,注意類名是接口名+Stub
- 開啓本地存根
創建一個UserServiceStub類
一定要在與UserService接口同包的路徑下去創建UserServiceStub,否則會找不到這個類
public class UserServiceStub implements UserService {
// 相當於是UserService的一個代理對象,當我們校驗完成後,可以通過這個代理對象調用服務
private final UserService userService;
public UserServiceStub(UserService userService) {
this.userService = userService;
}
public List<User> getUserList() {
System.out.println("校驗參數中......");
if (true) {
// 校驗成功,我們就繼續調用服務
return userService.getUserList();
} else {
// 校驗失敗
return new ArrayList<User>();
}
}
}
開啓本地存根
在這個版本中,如果你是以接口+Stub的這種形式創建的類,那麼當你創建完成後,本地存根其實就默認開啓了,但是最好還是顯示聲明一下,聲明即可在服務提供方聲明,也可以在服務消費方聲明,但是我們最好在提供方去聲明,統一管理服務。
設置stub爲true即可
@Service(version = "1.0",stub = "true")
6.7 本地僞裝
這個就是用服務降級來實現的,當服務提供方全部掛掉後,客戶端不拋出異常,而是通過 Mock 數據返回結果