Spring Boot Dubbo 入門
1. 概述
在 2019.05.21 號,在經歷了 1 年多的孵化,Dubbo 終於迎來了 Apache 畢業。在這期間,Dubbo 做了比較多的功能迭代,提供了 NodeJS、Python、Go 等語言的支援,也舉辦了多次社群活動,在網上的“罵聲”也少了。
艿艿:事實上,大多數成熟的開源專案,都是 KPI 驅動,又或者背後有商業化支撐。
作為一個長期使用,並且堅持使用 Dubbo 的開發者,還是比較愉快的。可能,又經歷了一次技術正確的選擇。當然,更愉快的是,Spring Cloud Alibaba 貌似,也已經完成孵化,雙劍合併,biubiubiu 。
可能胖友有些胖友對 Dubbo 不是很瞭解,這裡艿艿先簡單介紹下:
FROM Dubbo 官網
Apache Dubbo |ˈdʌbəʊ| 是一款高效能、輕量級的開源 Java RPC 框架,它提供了三大核心能力:面向介面的遠端方法呼叫,智慧容錯和負載均衡,以及服務自動註冊和發現。
圖中,一共涉及到 5 個角色:
- Registry 註冊中心,用於服務的註冊與發現。
- Provider 服務提供者,通過向 Registry 註冊服務。
- Consumer 服務消費者,通過從 Registry 發現服務。後續直接呼叫 Provider ,無需經過 Registry 。
- Monitor 監控中心,統計服務的呼叫次數和呼叫時間。
- Container 服務執行容器。
FROM 《Dubbo 文件 —— 架構》
呼叫關係說明(注意,和上圖的數字,和下面的步驟是一一對應的):
- 服務容器負責啟動,載入,執行服務提供者。
- 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
- 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
- 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者。
- 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。
- 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心。
本文的重心,在於一起入門 Provider 和 Consumer 的程式碼編寫,這也是實際專案開發中,我們涉及到的角色。
Dubbo 提供了比較多的配置方式,日常開發中主要使用的是 XML 配置 和 註解配置 。我們分別會在 「2. XML 配合」 和 「3. 註解配置」 小節來入門。
考慮到現在 Dubbo 已經提供了 dubbo-spring-boot-project
專案,整合到 Spring Boot 體系中,而大家都基本採用 Spring Boot 框架,所以我們就不像 Dubbo 官方文件 一樣,提供的是 Spring 環境下的示例,而是 Spring Boot 環境下。
2. XML 配置
示例程式碼對應倉庫:lab-30-dubbo-xml-demo 。
本小節的示例,需要建立三個 Maven 專案,如下圖所示:
user-rpc-service-api
專案:服務介面,定義 Dubbo Service API 介面,提供給消費者使用。詳細程式碼,我們在 「2.1 API」 講解。user-rpc-service-provider
專案:服務提供者,實現user-rpc-service-api
專案定義的 Dubbo Service API 介面,提供相應的服務。詳細程式碼,我們在 「2.2 Provider」 中講解。user-rpc-service-consumer
專案:服務消費者,會呼叫user-rpc-service-provider
專案提供的 Dubbo Service 服務。詳細程式碼,我們在 「2.3 Consumer」 中講解。
2.1 API
對應 user-rpc-service-api
專案,服務介面,定義 Dubbo Service API 介面,提供給消費者使用。
2.1.1 UserDTO
在 cn.iocoder.springboot.lab30.rpc.dto
包下,建立用於 Dubbo Service 傳輸類。這裡,我們建立 UserDTO 類,使用者資訊 DTO 。程式碼如下:
// UserDTO.java
public class UserDTO implements Serializable {
/**
* 使用者編號
*/
private Integer id;
/**
* 暱稱
*/
private String name;
/**
* 性別
*/
private Integer gender;
// ... 省略 set/get 方法
}
注意,要實現 java.io.Serializable
介面。因為,Dubbo RPC 會涉及遠端通訊,需要序列化和反序列化。
2.1.2 UserRpcService
在 cn.iocoder.springboot.lab30.rpc.api
包下,建立 Dubbo Service API 介面。這裡,我們建立 UserRpcService 介面,使用者服務 RPC Service 介面。程式碼如下:
// UserRpcService.java
public interface UserRpcService {
/**
* 根據指定使用者編號,獲得使用者資訊
*
* @param id 使用者編號
* @return 使用者資訊
*/
UserDTO get(Integer id);
}
2.2 Provider
對應 user-rpc-service-provider
專案,服務提供者,實現 user-rpc-service-api
專案定義的 Dubbo Service API 介面,提供相應的服務。
2.2.1 引入依賴
在 pom.xml
檔案中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-rpc-service</artifactId>
<dependencies>
<!-- 引入定義的 Dubbo API 介面 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>user-rpc-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入 Spring Boot 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 實現對 Dubbo 的自動化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<!-- 使用 Zookeeper 作為註冊中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
-
因為我們希望實現對 Dubbo 的自動化配置,所以引入
dubbo-spring-boot-starter
依賴。 -
因為我們希望使用 Zookeeper 作為註冊中心,所以引入
curator-framework
和curator-recipes
依賴。可能胖友不太瞭解 Apache Curator 框架,這裡我們看一段簡介:FROM https://www.oschina.net/p/curator
Zookeeper 的客戶端呼叫過於複雜,Apache Curator 就是為了簡化Zookeeper 客戶端呼叫而生,利用它,可以更好的使用 Zookeeper。 * 雖然說,目前阿里正在大力推廣 Nacos 作為 Dubbo 的註冊中心,但是大多數團隊,採用的還是 Zookeeper 為主。 * 對了,如果胖友不知道怎麼安裝 Zookeeper ,可以看看 《芋道 Zookeeper 極簡入門》 文章。
2.2.2 應用配置檔案
在 resources
目錄下, 建立 application.yml
配置檔案,新增 Dubbo 相關的配置,如下:
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-provider # 應用名
# Dubbo 註冊中心配
registry:
address: zookeeper://127.0.0.1:2181 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 服務提供者協議配置
protocol:
port: -1 # 協議埠。使用 -1 表示隨機埠。
name: dubbo # 使用 `dubbo://` 協議。更多協議,可見 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文件
# Dubbo 服務提供者配置
provider:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.0
-
dubbo-spring-boot-starter
依賴,會根據dubbo
配置項,實現對 Dubbo 的自動化配置。下面呢,我們來逐個配置項看看。艿艿:本小節,我們的 「XML 配置」 ,指的是使用 XML 來配置 Dubbo Service 服務。如果胖友想看純粹的全量 XML 配置,可以看看 《Dubbo 官方文件 —— XML 配置》 。
-
dubbo.application
配置項,Dubbo 應用資訊配置。更多屬性,可見 ApplicationConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:application》 。 -
dubbo.registry
配置項,Dubbo 註冊中心配置。更多屬性,可見 RegistryConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:registry》 。 -
dubbo.protocol
配置項,Dubbo 服務提供者協議配置。更多屬性,可見 ProtocolConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:protocol》 。 -
dubbo.provider
配置項,Dubbo 服務提供者配置。更多屬性,可見 ProviderConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:provider》 。 -
dubbo.provider.UserRpcService
配置項,是我們自定義的,設定每個 Service 服務的配置。更多屬性,可見 ServiceConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:service》 。
2.2.3 UserRpcServiceImpl
在 cn.iocoder.springboot.lab30.rpc.service
包下,建立 Dubbo Service 實現類。這裡,我們建立 UserRpcServiceImpl 類,使用者服務 RPC Service 實現類。程式碼如下:
// UserRpcServiceImpl.java
@Service
public class UserRpcServiceImpl implements UserRpcService {
@Override
public UserDTO get(Integer id) {
return new UserDTO().setId(id)
.setName("沒有暱稱:" + id)
.setGender(id % 2 + 1); // 1 - 男;2 - 女
}
}
- 實現 UserRpcService 介面,提供 UserRpcService Dubbo 服務。
- 注意,在類上新增了 Spring
@Service
註解,暴露出 UserRpcServiceImpl Bean 物件。? 後續,我們會將該 Bean 暴露成 UserRpcService Dubbo 服務,註冊其到註冊中心中,並提供相應的 Dubbo 服務。
2.2.4 Dubbo XML 配置檔案
在 resources
目錄下, 建立 dubbo.xml
配置檔案,新增 Dubbo 的 Service 服務提供者,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 服務提供者暴露服務配置 -->
<dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
version="${dubbo.provider.UserRpcService.version}" />
</beans>
- 使用 Dubbo 自定義的 Spring
<dubbo:service>
標籤,配置我們 「2.2.3 UserRpcServiceImpl」 成 UserRpcService 的 Dubbo 服務提供者。
更多 <dubbo:service>
標籤的屬性的說明,可見 《Dubbo 文件 —— dubbo:service》 。
2.2.5 ProviderApplication
建立 ProviderApplication 類,用於啟動該專案,提供 Dubbo 服務。程式碼如下:
// ProviderApplication.java
@SpringBootApplication
@ImportResource("classpath:dubbo.xml")
public class ProviderApplication {
public static void main(String[] args) {
// 啟動 Spring Boot 應用
SpringApplication.run(ProviderApplication.class, args);
}
}
- 在類上,新增
@ImportResource
註解,引入dubbo.xml
配置檔案。
執行 #main(String[] args)
方法,啟動專案。控制檯列印日誌如下:
// ... 省略其它日誌
2019-12-01 22:40:34.721 INFO 64176 --- [pool-1-thread-1] .b.c.e.AwaitingNonWebApplicationListener : [Dubbo] Current Spring Boot Application is await...
- 看到該日誌內容,意味著啟動成功。
我們來使用 Zookeeper 客戶端,檢視 UserRpcService 服務是否註冊成功。操作流程如下:
# 使用 Zookeeper 自帶的客戶端,連線到 Zookeeper 伺服器
$ bin/zkCli.sh
# 檢視 /dubbo 目錄下的所有服務。
# 此時,我們檢視到了 UserRpcService 服務
$ ls /dubbo
[cn.iocoder.springboot.lab30.rpc.api.UserRpcService]
# 檢視 /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService 目錄下的儲存情況。
# 此時,我們看到了 consumers 消費者資訊,providers 提供者資訊,routers 路由資訊,configurators 配置資訊。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService
[consumers, configurators, routers, providers]
# 檢視 UserRpcService 服務的節點列表
# 此時,可以看到有一個節點,就是我們剛啟動的服務提供者。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService/providers
[dubbo%3A%2F%2F10.171.1.115%3A20880%2Fcn.iocoder.springboot.lab30.rpc.api.UserRpcService%3Fanyhost%3Dtrue%26application%3Duser-service-provider%26bean.name%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26methods%3Dget%26pid%3D64176%26release%3D2.7.4.1%26revision%3D1.0.0%26side%3Dprovider%26timeout%3D1000%26timestamp%3D1575211234365%26version%3D1.0.0]
想要了解更多 Dubbo 是如何使用 Zookeeper 儲存資料的,可以看看 《Dubbo 文件 —— Zookeeper 註冊中心》 文件。
2.3 Consumer
對應 user-rpc-service-consumer
專案,服務消費者,會呼叫 user-rpc-service-provider
專案提供的 Dubbo Service 服務。
2.3.1 引入依賴
在 pom.xml
檔案中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-rpc-service-consumer</artifactId>
<dependencies>
<!-- 引入定義的 Dubbo API 介面 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>user-rpc-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入 Spring Boot 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 實現對 Dubbo 的自動化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<!-- 使用 Zookeeper 作為註冊中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
- 和 「2.2.1 引入依賴」 一模一樣,除了
<artifactId />
改成了"user-rpc-service-consumer"
值。
2.3.2 應用配置檔案
在 resources
目錄下, 建立 application.yml
配置檔案,新增 Dubbo 相關的配置,如下:
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-consumer # 應用名
# Dubbo 註冊中心配置
registry:
address: zookeeper://127.0.0.1:2181 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 消費者配置
consumer:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.0
- 和 「2.2.2 應用配置檔案」 看起來有點類似,我們僅僅說說差異性。
- 去掉
dubbo.protocol
配置項,因為我們是作為 Dubbo 服務的消費者,所以無需新增 Dubbo 服務提供者協議配置。 dubbo.consumer
配置項,Dubbo 服務消費者配置。更多屬性,可見 ConsumerConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:consumer》 。dubbo.consumer.UserRpcService
配置項,是我們自定義的,設定每個 Service 服務的配置。更多屬性,可見 ReferenceConfig 類。每個屬性的說明,可見 《Dubbo 文件 —— dubbo:reference》 。
2.3.3 Dubbo XML 配置檔案
在 resources
目錄下,建立 dubbo.xml
配置檔案,新增 Dubbo 的 Service 服務引用者,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 服務消費者引用服務配置 -->
<dubbo:reference id="userService" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
version="${dubbo.consumer.UserRpcService.version}"/>
</beans>
- 使用 Dubbo 自定義的 Spring
<dubbo:reference>
標籤,引用 UserRpcService 介面對應的 Dubbo Service 服務,並建立一個 Bean 編號為"userService"
的 Bean 物件。這樣,我們在 Spring 中,就可以直接注入 UserRpcService Bean ,後續就可以像一個“本地”的 UserRpcService 進行呼叫使用。
更多 <dubbo:reference>
標籤的屬性的說明,可見 《Dubbo 文件 —— dubbo:reference》 。
2.3.4 ConsumerApplication
建立 ConsumerApplication 類,用於啟動該專案,呼叫 Dubbo 服務。程式碼如下:
// ConsumerApplication.java
@SpringBootApplication
@ImportResource("classpath:dubbo.xml")
public class ConsumerApplication {
public static void main(String[] args) {
// 啟動 Spring Boot 應用
ConfigurableApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
}
@Component
public class UserRpcServiceTest implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private UserRpcService userRpcService;
@Override
public void run(String... args) throws Exception {
UserDTO user = userRpcService.get(1);
logger.info("[run][發起一次 Dubbo RPC 請求,獲得使用者為({})", user);
}
}
}
- 在類上,新增
@ImportResource
註解,引入dubbo.xml
配置檔案。 - 在 UserRpcServiceTest 中,我們使用
@Resource
註解,引用通過<dubbo:reference />
配置的引用的 UserRpcService 服務對應的 UserRpcService Bean 。
執行 #main(String[] args)
方法,啟動專案。控制檯列印日誌如下:
2019-12-01 23:15:47.380 INFO 65726 --- [ main] r.ConsumerApplication$UserRpcServiceTest : [run][發起一次 Dubbo RPC 請求,獲得使用者為(cn.iocoder.springboot.lab30.rpc.dto.UserDTO@a0a9fa5)
- 我們在應用啟動完成後,成功的發起了一次 UserRpcService 的 Dubbo RPC 的呼叫。
我們來使用 Zookeeper 客戶端,檢視 UserRpcService 服務是否多了一個消費者。操作流程如下:
# 使用 Zookeeper 自帶的客戶端,連線到 Zookeeper 伺服器
$ bin/zkCli.sh
# 檢視 UserRpcService 服務的消費者列表
# 此時,可以看到有一個節點,就是我們剛啟動的服務消費者。
$ ls /dubbo/cn.iocoder.springboot.lab30.rpc.api.UserRpcService/consumers
[consumer%3A%2F%2F10.171.1.115%2Fcn.iocoder.springboot.lab30.rpc.api.UserRpcService%3Fapplication%3Duser-service-consumer%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26interface%3Dcn.iocoder.springboot.lab30.rpc.api.UserRpcService%26lazy%3Dfalse%26methods%3Dget%26pid%3D65726%26qos.enable%3Dfalse%26release%3D2.7.4.1%26revision%3D1.0.0%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D1000%26timestamp%3D1575213346748%26version%3D1.0.0]
至此,我們已經完成了使用 XML 配置的方式,在 Spring Boot 中使用 Dubbo 的入門。? 雖然篇幅長了一點點,但是還是比較簡單的。個人建議的話,此時此刻僅僅是看到這裡,但是並沒有手敲程式碼的胖友,可以趕緊開啟 IDEA 自己敲(“抄”)一波,嘿嘿。
3. 註解配置
示例程式碼對應倉庫:lab-30-dubbo-annotations-demo 。
本小節的示例,需要建立三個 Maven 專案,如下圖所示:
user-rpc-service-api-02
專案:服務介面,定義 Dubbo Service API 介面,提供給消費者使用。詳細程式碼,我們在 「3.1 API」 講解。user-rpc-service-provider-02
專案:服務提供者,實現user-rpc-service-api-02
專案定義的 Dubbo Service API 介面,提供相應的服務。詳細程式碼,我們在 「3.2 Provider」 中講解。user-rpc-service-consumer-02
專案:服務消費者,會呼叫user-rpc-service-provider-02
專案提供的 Dubbo Service 服務。詳細程式碼,我們在 「3.3 Consumer」 中講解。
? 本小節的內容上,和 「2.1 XML 配置」 會比較接近,所以會講的相對簡略,重點說差異。
艿艿:為了保證閱讀體驗,即使一致的內容,艿艿還是貼一遍比較好。
3.1 API
對應 user-rpc-service-api-02
專案,服務介面,定義 Dubbo Service API 介面,提供給消費者使用。
3.1.1 UserDTO
和 「2.1.1 UserDTO」 一致。
在 cn.iocoder.springboot.lab30.rpc.dto
包下,建立用於 Dubbo Service 傳輸類。這裡,我們建立 UserDTO 類,使用者資訊。程式碼如下:
// UserDTO.java
public class UserDTO implements Serializable {
/**
* 使用者編號
*/
private Integer id;
/**
* 暱稱
*/
private String name;
/**
* 性別
*/
private Integer gender;
// ... 省略 set/get 方法
}
注意,要實現 java.io.Serializable
介面。因為,Dubbo RPC 會涉及遠端通訊,需要序列化和反序列化。
3.1.2 UserRpcService
和 3.1.2 UserRpcService」 一致。
在 cn.iocoder.springboot.lab30.rpc.api
包下,建立 Dubbo Service API 介面。這裡,我們建立 UserRpcService 介面,使用者服務 RPC Service 介面。程式碼如下:
// UserRpcService.java
public interface UserRpcService {
/**
* 根據指定使用者編號,獲得使用者資訊
*
* @param id 使用者編號
* @return 使用者資訊
*/
UserDTO get(Integer id);
}
3.2 Provider
對應 user-rpc-service-provider-02
專案,服務提供者,實現 user-rpc-service-api
專案定義的 Dubbo Service API 介面,提供相應的服務。
3.2.1 引入依賴
和 「2.2.1 引入依賴」 一致。
在 pom.xml
檔案中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-rpc-service-provider-02</artifactId>
<dependencies>
<!-- 引入定義的 Dubbo API 介面 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>user-rpc-service-api-02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入 Spring Boot 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 實現對 Dubbo 的自動化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<!-- 使用 Zookeeper 作為註冊中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
3.2.2 應用配置檔案
在 resources
目錄下, 建立 application.yml
配置檔案,新增 Dubbo 相關的配置,如下:
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-provider # 應用名
# Dubbo 註冊中心配
registry:
address: zookeeper://127.0.0.1:2181 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 服務提供者協議配置
protocol:
port: -1 # 協議埠。使用 -1 表示隨機埠。
name: dubbo # 使用 `dubbo://` 協議。更多協議,可見 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文件
# Dubbo 服務提供者配置
provider:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.
# 配置掃描 Dubbo 自定義的 @Service 註解,暴露成 Dubbo 服務提供者
scan:
base-packages: cn.iocoder.springboot.lab30.rpc.service
和 「2.2.2 應用配置」 基本一致,差異在於多出了 dubbo.scan.base-packages
配置項,配置掃描的基礎路徑,後續會根據該路徑,掃描使用了 Dubbo 自定義的 @Service
註解的 Service 類們,將它們暴露成 Dubbo 服務提供者。
如此,我們就不需要使用 「2.2.4 Dubbo XML 配置檔案」 ,配置暴露的 Service 服務,而是通過 Dubbo 定義的 @Service
註解。
3.2.3 UserRpcServiceImpl
在 cn.iocoder.springboot.lab30.rpc.service
包下,建立 Dubbo Service 實現類。這裡,我們建立 UserRpcServiceImpl 類,使用者服務 RPC Service 實現類。程式碼如下:
// UserRpcServiceImpl.java
@Service(version = "${dubbo.provider.UserRpcService.version}")
public class UserRpcServiceImpl implements UserRpcService {
@Override
public UserDTO get(Integer id) {
return new UserDTO().setId(id)
.setName("沒有暱稱:" + id)
.setGender(id % 2 + 1); // 1 - 男;2 - 女
}
}
- 在類上,我們新增的是 Dubbo 定義的
@Service
註解。並且,在該註解裡,我們可以新增該 Service 服務的配置。當然,每個屬性和<dubbo:service />
標籤是基本一致的。也因此,每個屬性的說明,還可見 《Dubbo 文件 —— dubbo:service》 。
3.2.4 ProviderApplication
建立 ProviderApplication 類,用於啟動該專案,提供 Dubbo 服務。程式碼如下:
// ProviderApplication.java
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
// 啟動 Spring Boot 應用
SpringApplication.run(ProviderApplication.class, args);
}
}
- 在類上,無需新增
@ImportResource
註解,引入dubbo.xml
配置檔案。
艿艿:後續的操作,和 「2.2.5 ProviderApplication」 是一致的,這裡就不重複贅述了。
3.3 Consumer
對應 user-rpc-service-consumer-02
專案,服務消費者,會呼叫 user-rpc-service-provider-02
專案提供的 Dubbo Service 服務。
3.3.1 引入依賴
和 「2.3.1 引入依賴」 一致。
在 pom.xml
檔案中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-rpc-service-consumer-02</artifactId>
<dependencies>
<!-- 引入定義的 Dubbo API 介面 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>user-rpc-service-api-02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入 Spring Boot 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 實現對 Dubbo 的自動化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<!-- 使用 Zookeeper 作為註冊中心 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
- 和 「3.2.1 引入依賴」 一模一樣,除了
<artifactId />
改成了"user-rpc-service-consumer-02"
值。
3.3.2 應用配置檔案
和 「2.3.2 應用配置檔案」 一致。
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-consumer # 應用名
# Dubbo 註冊中心配置
registry:
address: zookeeper://127.0.0.1:2181 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 消費者配置
consumer:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.0
3.3.3 ConsumerApplication
建立 ConsumerApplication 類,用於啟動該專案,呼叫 Dubbo 服務。程式碼如下:
// ConsumerApplication.java
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
// 啟動 Spring Boot 應用
ConfigurableApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
}
@Component
public class UserRpcServiceTest implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Reference(version = "${dubbo.consumer.UserRpcService.version}")
private UserRpcService userRpcService;
@Override
public void run(String... args) throws Exception {
UserDTO user = userRpcService.get(1);
logger.info("[run][發起一次 Dubbo RPC 請求,獲得使用者為({})", user);
}
}
}
- 在類上,無需新增
@ImportResource
註解,引入dubbo.xml
配置檔案。 - 在 UserRpcServiceTest 中,我們使用 Dubbo 定義的
@Reference
註解,**“直接”**引用的 UserRpcService 服務對應的 UserRpcService Bean 。並且,在該註解裡,我們可以新增該 Service 服務的配置。當然,每個屬性和<dubbo:reference />
標籤是基本一致的。也因此,每個屬性的說明,還可見 《Dubbo 文件 —— dubbo:reference》 。
艿艿:後續的操作,和 「2.3.4 ConsumerApplication」 是一致的,這裡就不重複贅述了。
3.4 選擇註解還是 XML 配置?
艿艿個人傾向的話,偏向使用 XML 配置。
主要原因是,@Reference
註解,每次引用服務的時候,都需要在註解上新增好多配置的屬性。這樣,服務的引用的配置後就散落到各個類裡了。
雖然說,我們可以把 @Reference
註解的配置的屬性值,放到 application.yaml
等等配置檔案裡,但是如果我們要給相同 Service 的多個 @Reference
增加新的配置屬性時,就要每個註解都修改一遍。
對於這種情況,XML 配置的方式,只要修改一下該 Service 的 XML 配置,就可以全部生效了。
4. 引數驗證
引數校驗,對於提供 API 呼叫的服務來說,必然是必不可少的。在 《芋道 Spring Boot 引數校驗 Validation 入門》 中,我們已經看了如何在 SpringMVC 和本地的 Service 使用引數校驗的示例。
本小節,我們來學習下,如何在 Dubbo RPC Service 中,使用引數校驗。在 《Dubbo 文件 —— 引數驗證》 中,對該功能的描述如下:
引數驗證功能是基於 JSR303 實現的,使用者只需標識 JSR303 標準的驗證 annotation,並通過宣告 filter 來實現驗證。
- 我們在 《Spring Boot 引數校驗 Validation 入門》 中學習的,也是基於 JSR303 規範實現的,所以在使用上,是基本一致的。? 有統一的規範,真好。
下面,我們開始在 「2. XML 配置」 小節的 lab-30-dubbo-xml-demo 示例專案,進行修改,新增引數校驗的功能。
4.1 API
本小節,我們來看看對 user-rpc-service-api
專案的改造。
4.1.1 引入依賴
修改 pom.xml
檔案中,引入相關依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-30-dubbo-xml-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-rpc-service-api</artifactId>
<dependencies>
<!-- 引數校驗相關依賴 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId> <!-- JSR 引數校驗規範 API -->
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <!-- JSR 引數校驗規範實現,我們使用 hibernate-validator -->
<version>6.0.18.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId> <!-- 可能涉及到 EL 表達,所以引入,否則 hibernate-validator 在初始化會報錯 -->
<version>3.0.1-b11</version>
</dependency>
</dependencies>
</project>
4.1.2 UserAddDTO
在 cn.iocoder.springboot.lab30.rpc.dto
包下,建立 UserAddDTO 類,使用者新增 DTO。程式碼如下:
// UserAddDTO.java
public class UserAddDTO implements Serializable {
/**
* 暱稱
*/
@NotEmpty(message = "暱稱不能為空")
@Length(min = 5, max = 16, message = "賬號長度為 5-16 位")
private String name;
/**
* 性別
*/
@NotNull(message = "性別不能為空")
private Integer gender;
// ... 省略 set/get 方法
}
- 在
name
和gender
屬性上,我們新增了引數校驗的註解。
4.1.3 UserRpcService
修改 UserRpcService 介面,程式碼如下:
// UserRpcService.java
public interface UserRpcService {
/**
* 根據指定使用者編號,獲得使用者資訊
*
* @param id 使用者編號
* @return 使用者資訊
*/
UserDTO get(@NotNull(message = "使用者編號不能為空") Integer id)
throws ConstraintViolationException;
/**
* 新增新使用者,返回新新增的使用者編號
*
* @param addDTO 新增的使用者資訊
* @return 使用者編號
*/
Integer add(UserAddDTO addDTO)
throws ConstraintViolationException;
}
- 在已有的
#get(Integer id)
方法上,新增@NotNull
註解,校驗使用者編號不允許傳空。 - 新增
#add(UserAddDTO addDTO)
方法,新增新使用者,返回新新增的使用者編號。我們已經在 UserAddDTO 類,新增了相應的引數校驗的註解。 - 注意,因為引數校驗不通過時,會丟擲 ConstraintViolationException 異常,所以需要在介面的方法,顯示使用
throws
註明。具體的原因,可以看看 《淺談 Dubbo 的 ExceptionFilter 異常處理》 文章,瞭解下 Dubbo 的異常處理機制。
4.2 Provider
本小節,我們來看看對 user-rpc-service-provider
專案的改造。
4.2.1 UserRpcServiceImpl
修改 UserRpcServiceImpl 類,簡單實現下 #add(UserAddDTO addDTO)
方法。程式碼如下:
// UserRpcServiceImpl.java
@Override
public Integer add(UserAddDTO addDTO) {
return (int) (System.currentTimeMillis() / 1000); // 嘿嘿,隨便返回一個 id
}
4.2.2 Dubbo XML 配置檔案
修改 dubbo.xml
配置檔案,開啟 UserRpcService 的引數校驗功能。配置如下:
<dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
version="${dubbo.provider.UserRpcService.version}" validation="true" />
- 這裡,我們將
validation
設定為"true"
,開啟 Dubbo 服務提供者的 UserRpcService 服務的引數校驗的功能。
? 如果胖友想把 Dubbo 服務提供者的所有 Service 服務的引數校驗都開啟,可以修改 application.yaml
配置檔案,增加 dubbo.provider.validation = true
配置。
4.3 Consumer
本小節,我們來看看對 user-rpc-service-consumer
專案的改造。
4.3.1 Dubbo XML 配置檔案
修改 dubbo.xml
配置檔案,開啟 UserRpcService 的引數校驗功能。配置如下:
<dubbo:reference id="userService" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
version="${dubbo.consumer.UserRpcService.version}" validation="true" />
- 這裡,我們將
validation
設定為"true"
,開啟 Dubbo 服務消費者的 UserRpcService 服務的引數校驗的功能。
? 如果胖友想把 Dubbo 服務消費者的所有 Service 服務的引數校驗都開啟,可以修改 application.yaml
配置檔案,增加 dubbo.consumer.validation = true
配置。
可能胖友會有疑惑,服務提供者和服務消費者的 validation = true
,都是開啟引數校驗規則,會有什麼區別呢?Dubbo 內建 ValidationFilter 過濾器,實現引數校驗的功能,可作用於服務提供者和服務消費者。效果如下:
- 如果服務消費者開啟引數校驗,請求引數校驗不通過時,結束請求,丟擲 ConstraintViolationException 異常。即,不會向服務提供者發起請求。
- 如果服務提供者開啟引數校驗,請求引數校驗不通過時,結束請求,丟擲 ConstraintViolationException 異常。即,不會執行後續的業務邏輯。
實際專案在使用時,至少要開啟服務提供者的引數校驗功能。
4.3.2 ConsumerApplication
修改 ConsumerApplication 類,增加呼叫 UserRpcService 服務時,引數校驗不通過的示例。程式碼如下:
// ConsumerApplication.java
@Component
public class UserRpcServiceTest02 implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private UserRpcService userRpcService;
@Override
public void run(String... args) throws Exception {
// 獲得使用者
try {
// 發起呼叫
UserDTO user = userRpcService.get(null); // 故意傳入空的編號,為了校驗編號不通過
logger.info("[run][發起一次 Dubbo RPC 請求,獲得使用者為({})]", user);
} catch (Exception e) {
logger.error("[run][獲得使用者發生異常,資訊為:[{}]", e.getMessage());
}
// 新增使用者
try {
// 建立 UserAddDTO
UserAddDTO addDTO = new UserAddDTO();
addDTO.setName("yudaoyuanmayudaoyuanma"); // 故意把名字打的特別長,為了校驗名字不通過
addDTO.setGender(null); // 不傳遞性別,為了校驗性別不通過
// 發起呼叫
userRpcService.add(addDTO);
logger.info("[run][發起一次 Dubbo RPC 請求,新增使用者為({})]", addDTO);
} catch (Exception e) {
logger.error("[run][新增使用者發生異常,資訊為:[{}]", e.getMessage());
}
}
}
- 新增了兩段程式碼,分別呼叫 UserRpcService 服務的
#get(Integer id)
和#add(UserAddDTO addDTO)
方法,並且是引數不符合校驗條件的示例。
執行 #main(String[] args)
方法,啟動專案。控制檯列印日誌如下:
// 呼叫 UserRpcService 服務的 `#get(Integer id)` 方法,引數不通過
2019-12-01 13:19:08.836 ERROR 7055 --- [ main] ConsumerApplication$UserRpcServiceTest02 : [run][獲得使用者發生異常,資訊為:[Failed to validate service: cn.iocoder.springboot.lab30.rpc.api.UserRpcService, method: get, cause: [ConstraintViolationImpl{interpolatedMessage='使用者編號不能為空', propertyPath=getArgument0, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.api.UserRpcService_GetParameter_java.lang.Integer, messageTemplate='使用者編號不能為空'}]]
// 呼叫 UserRpcService 服務的 `#add(UserAddDTO addDTO)` 方法,引數不通過
2019-12-01 13:19:08.840 ERROR 7055 --- [ main] ConsumerApplication$UserRpcServiceTest02 : [run][新增使用者發生異常,資訊為:[Failed to validate service: cn.iocoder.springboot.lab30.rpc.api.UserRpcService, method: add, cause: [ConstraintViolationImpl{interpolatedMessage='性別不能為空', propertyPath=gender, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.dto.UserAddDTO, messageTemplate='性別不能為空'}, ConstraintViolationImpl{interpolatedMessage='賬號長度為 5-16 位', propertyPath=name, rootBeanClass=class cn.iocoder.springboot.lab30.rpc.dto.UserAddDTO, messageTemplate='賬號長度為 5-16 位'}]]
- 上述賊長的兩段日誌,我們可以看到兩次 UserRpcService 服務的呼叫,都丟擲了 ConstraintViolationException 異常。
4.4 存在的問題
如果我們關閉掉服務消費者的引數校驗功能,而只使用服務提供者的引數校驗功能的情況下,當引數校驗不通過時,因為 Hibernate ConstraintDescriptorImpl 沒有預設空構造方法,所以 Hessian 反序列化時,會丟擲 HessianProtocolException 異常。詳細如下:
Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiated
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:316)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:201)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406)
目前有兩種解決方案:
- 方案一,不要關閉掉服務消費者的引數校驗功能。
- 方案二,參考 《Dubbo 使用 JSR303 框架 hibernate-validator 遇到 ConstraintDescriptorImpl could not be instantiated》 文章的方法三。
- 方案三,Service 介面上,不要丟擲 ConstraintViolationException 異常。這樣,該異常就可以被 Dubbo 內建的 ExceptionFilter 封裝成 RuntimeException 異常,就不會存在反序列化的問題。
不過目前方案二,提交在 https://github.com/apache/incubator-dubbo/pull/1708 的 PR 程式碼,已經被 Dubbo 開發團隊否決了。所以,目前建議還是採用方案一來解決。
5. 自定義實現擴充點
在「4. 引數校驗」 小節中,我們入門了 Dubbo 提供的引數校驗的功能,它是由 ValidationFilter 過濾器,通過攔截請求,根據我們新增 JSR303 定義的註解,校驗引數是否正確。在 Dubbo 框架中,還提供了 AccessLogFilter、ExceptionFilter 等等過濾器,他們都屬於 Dubbo Filter 介面的實現類。
而實際上,Filter 是 Dubbo 定義的 呼叫攔截 擴充點。除了 Filter 擴充點,Dubbo 還定義了 協議、路由、註冊中心 等等擴充點。如下圖所示:
而這些 Dubbo 擴充點,通過 Dubbo SPI 機制,進行載入。可能胖友對 Dubbo SPI 機制有點懵逼。嘿嘿,一定沒有好好讀過 Dubbo 的官方文件:
FROM 《Dubbo 擴充套件點載入》
Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。
Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。
Dubbo 改進了 JDK 標準的 SPI 的以下問題:
- JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
- 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過
getName()
獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當使用者執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。- 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。
下面,我們實現一個對 ExceptionFilter 增強的過濾器,實現即使 Service API 介面上,未定義 ServiceException、ConstraintViolationException 等異常,也不會自動封裝成 RuntimeException 。? 畢竟,要求每個開發同學記得在 Service API 介面上,新增 ServiceException、ConstraintViolationException 等異常,是挺困難的事情,總是可能不經意遺忘。
下面,我們繼續在 「2. XML 配置」 小節的 lab-30-dubbo-xml-demo 示例專案,進行修改,新增自定義 ExceptionFilter 增強的過濾器的功能。
艿艿:關於本小節的內容,艿艿希望胖友有看過 《芋道 Spring Boot SpringMVC 入門》 的 「4. 全域性統一返回」 和 「5. 全域性異常處理」 小節的內容,因為涉及到的思路是一致的。
5.1 API
本小節,我們來看看對 user-rpc-service-api
專案的改造。
5.1.1 ServiceExceptionEnum
在 cn.iocoder.springboot.lab30.rpc.core
包路徑,建立 ServiceExceptionEnum 列舉類,列舉專案中的錯誤碼。程式碼如下:
// ServiceExceptionEnum.java
public enum ServiceExceptionEnum {
// ========== 系統級別 ==========
SUCCESS(0, "成功"),
SYS_ERROR(2001001000, "服務端發生異常"),
MISSING_REQUEST_PARAM_ERROR(2001001001, "引數缺失"),
// ========== 使用者模組 ==========
USER_NOT_FOUND(1001002000, "使用者不存在"),
// ========== 訂單模組 ==========
// ========== 商品模組 ==========
;
/**
* 錯誤碼
*/
private int code;
/**
* 錯誤提示
*/
private String message;
ServiceExceptionEnum(int code, String message) {
this.code = code;
this.message = message;
}
// ... 省略 getting 方法
}
因為錯誤碼是全域性的,最好按照模組來拆分。如下是艿艿在 onemall 專案的實踐:
/**
* 服務異常
*
* 參考 https://www.kancloud.cn/onebase/ob/484204 文章
*
* 一共 10 位,分成四段
*
* 第一段,1 位,型別
* 1 - 業務級別異常
* 2 - 系統級別異常
* 第二段,3 位,系統型別
* 001 - 使用者系統
* 002 - 商品系統
* 003 - 訂單系統
* 004 - 支付系統
* 005 - 優惠劵系統
* ... - ...
* 第三段,3 位,模組
* 不限制規則。
* 一般建議,每個系統裡面,可能有多個模組,可以再去做分段。以使用者系統為例子:
* 001 - OAuth2 模組
* 002 - User 模組
* 003 - MobileCode 模組
* 第四段,3 位,錯誤碼
* 不限制規則。
* 一般建議,每個模組自增。
*/
5.1.2 ServiceException
在 cn.iocoder.springboot.lab30.rpc.core
包路徑,建立 ServiceException 異常類,繼承 RuntimeException 異常類,用於定義業務異常。程式碼如下:
public final class ServiceException extends RuntimeException {
/**
* 錯誤碼
*/
private Integer code;
public ServiceException() { // 建立預設構造方法,用於反序列化的場景。
}
public ServiceException(ServiceExceptionEnum serviceExceptionEnum) {
// 使用父類的 message 欄位
super(serviceExceptionEnum.getMessage());
// 設定錯誤碼
this.code = serviceExceptionEnum.getCode();
}
public ServiceException(ServiceExceptionEnum serviceExceptionEnum, String message) {
// 使用父類的 message 欄位
super(message);
// 設定錯誤碼
this.code = serviceExceptionEnum.getCode();
}
public Integer getCode() {
return code;
}
}
5.2 Provider
本小節,我們來看看對 user-rpc-service-provider
專案的改造。
5.2.1 DubboExceptionFilter
在 cn.iocoder.springboot.lab30.rpc.filter
包路徑,建立 DubboExceptionFilter ,繼承 ListenableFilter 抽象類,實現對 ExceptionFilter 增強的過濾器。程式碼如下:
// DubboExceptionFilter.java
@Activate(group = CommonConstants.PROVIDER)
public class DubboExceptionFilter extends ListenableFilter {
public DubboExceptionFilter() {
super.listener = new ExceptionListenerX();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
static class ExceptionListenerX extends ExceptionListener {
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
// 發生異常,並且非泛化呼叫
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
Throwable exception = appResponse.getException();
// <1> 如果是 ServiceException 異常,直接返回
if (exception instanceof ServiceException) {
return;
}
// <2> 如果是引數校驗的 ConstraintViolationException 異常,則封裝返回
if (exception instanceof ConstraintViolationException) {
appResponse.setException(this.handleConstraintViolationException((ConstraintViolationException) exception));
return;
}
}
// <3> 其它情況,繼續使用父類處理
super.onResponse(appResponse, invoker, invocation);
}
private ServiceException handleConstraintViolationException(ConstraintViolationException ex) {
// 拼接錯誤
StringBuilder detailMessage = new StringBuilder();
for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
// 使用 ; 分隔多個錯誤
if (detailMessage.length() > 0) {
detailMessage.append(";");
}
// 拼接內容到其中
detailMessage.append(constraintViolation.getMessage());
}
// 返回異常
return new ServiceException(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR,
detailMessage.toString());
}
}
static class ExceptionListener implements Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionListener.class);
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
// directly throw if it's checked exception
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
// directly throw if the exception appears in the signature
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return;
}
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return;
}
// otherwise, wrap with RuntimeException and throw back to the client
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return;
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
// For test purpose
public void setLogger(Logger logger) {
this.logger = logger;
}
}
}
- 在類上,新增
@Activate
註解,並設定"group = CommonConstants.PROVIDER"
屬性,將 DubboExceptionFilter 過濾器僅在服務提供者生效。 - 因為目前 Dubbo 原始碼改版,建議在對於 Filter 擴充點的實現,繼承 ListenableFilter 抽象類,更簡易的實現對呼叫結果的處理。
- 在構造方法中,我們建立了 ExceptionListenerX 類,作為
listener
監聽器。而 ExceptionListenerX 繼承自的 ExceptionListener 類,是我們直接從 Dubbo ExceptionFilter.ExceptionListener 複製過來的邏輯,為了保持 ExceptionFilter 原有邏輯的不變。下面,讓我們來看看 ExceptionListenerX 的實現程式碼:<1>
處,如果是 ServiceException 異常,直接返回。<2>
處,如果是引數校驗的 ConstraintViolationException 異常,則呼叫#handleConstraintViolationException(ConstraintViolationException ex)
方法,封裝成 ServiceException 異常,之後返回。<3>
處,其它情況,繼續使用父類 ExceptionListener 來處理。
這裡,可能有胖友對 ExceptionFilter 異常處理不是很瞭解,建議看看 《淺談 Dubbo 的 ExceptionFilter 異常處理》 文章。
另外,DubboExceptionFilter 是 「4.4 存在問題」 的方案二的一種變種解決方案。
5.2.2 Dubbo SPI 配置檔案
在 resources
目錄下,建立 META-INF/dubbo/
目錄,然後建立 org.apache.dubbo.rpc.Filter 配置檔案,配置如下:
dubboExceptionFilter=cn.iocoder.springboot.lab30.rpc.filter.DubboExceptionFilter
-
org.apache.dubbo.rpc.Filter
配置檔名,不要亂建立,就是 DubboExceptionFilter 對應的 Dubbo SPI 擴充點 Filter 。 - 該配置檔案裡的每一行,格式為
${擴充名}=${擴充類全名}
。這裡,我們配置了一個擴充名為dubboExceptionFilter
。
5.2.3 UserRpcServiceImpl
修改 UserRpcServiceImpl 類,修改下 #add(UserAddDTO addDTO)
方法,丟擲 ServiceException 異常。程式碼如下:
// UserRpcServiceImpl.java
@Override
public Integer add(UserAddDTO addDTO) {
// 【額外新增】這裡,模擬使用者已經存在的情況
if ("yudaoyuanma".equals(addDTO.getName())) {
throw new ServiceException(ServiceExceptionEnum.USER_EXISTS);
}
return (int) (System.currentTimeMillis() / 1000); // 嘿嘿,隨便返回一個 id
}
5.2.4 應用配置檔案
修改 application.yml
配置檔案,新增 dubbo.provider.filter=-exception
配置項,去掉服務提供者的 ExceptionFilter 過濾器。
如果胖友僅僅想去掉 UserRpcService 服務的 ExceptionFilter 過濾器,可以修改 dubbo.xml
配置檔案,配置如下:
<dubbo:service ref="userRpcServiceImpl" interface="cn.iocoder.springboot.lab30.rpc.api.UserRpcService"
version="${dubbo.provider.UserRpcService.version}" validation="true" filter="-exception" />
- 這裡,我們將
filter
設定為"-exception"
,去掉服務提供者的 UserRpcService 的 ExceptionFilter 過濾器。
當然,一般情況下啊,我們採用全域性配置,即通過 dubbo.provider.filter=-exception
配置項。
5.3 Consumer
本小節,我們來看看對 user-rpc-service-consumer
專案的改造。
5.3.1 ConsumerApplication
修改 ConsumerApplication 類,增加呼叫 UserRpcService 服務時,丟擲 ServiceException 異常的示例。程式碼如下:
// ConsumerApplication.java
@Component
public class UserRpcServiceTest03 implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private UserRpcService userRpcService;
@Override
public void run(String... args) {
// 新增使用者
try {
// 建立 UserAddDTO
UserAddDTO addDTO = new UserAddDTO();
addDTO.setName("yudaoyuanma"); // 設定為 yudaoyuanma ,觸發 ServiceException 異常
addDTO.setGender(1);
// 發起呼叫
userRpcService.add(addDTO);
logger.info("[run][發起一次 Dubbo RPC 請求,新增使用者為({})]", addDTO);
} catch (Exception e) {
logger.error("[run][新增使用者發生異常({}),資訊為:[{}]", e.getClass().getSimpleName(), e.getMessage());
}
}
}
- 新增了一段程式碼,呼叫 UserRpcService 服務的
#add(UserAddDTO addDTO)
方法,並且是丟擲 ServiceException 異常的示例。
執行 #main(String[] args)
方法,啟動專案。控制檯列印日誌如下:
2019-12-01 16:17:39.919 ERROR 14738 --- [ main] ConsumerApplication$UserRpcServiceTest03 : [run][新增使用者發生異常(ServiceException),資訊為:[使用者已存在]
- 我們可以看到,成功丟擲 ServiceException 異常,即使我們在 UserRpcService API 介面的
#add(UserAddDTO addDTO)
方法上,並未顯示throws
丟擲 UserRpcService 異常。
5.4 小結
實際上,因為我們把 ServiceException 放在了 Service API 所在的 Maven 專案裡,所以即使使用 Dubbo 內建的 ExceptionFilter 過濾器,並且 UserRpcService API 介面的 #add(UserAddDTO addDTO)
方法並未顯示 throws
丟擲 UserRpcService 異常,ExceptionFilter 也不會把 UserRpcService 封裝成 RuntimeException 異常。咳咳咳 ? 如果不瞭解的胖友,胖友在回看下 《淺談 Dubbo 的 ExceptionFilter 異常處理》 文章,結尾的“4. 把異常放到 provider-api 的 jar 包中”。
實際專案的 ExceptionFilter 增強封裝,可以看看艿艿在開源專案 onemall 中,會把 ServiceException 和 DubboExceptionFilter 放在 common-framework 框架專案中,而不是各個業務專案中。
6. 整合 Nacos
示例程式碼對應倉庫:
lab-30-dubbo-annotations-nacos
。
本小節我們來進行 Dubbo 和 Nacos 的整合,使用 Nacos 作為 Dubbo 的註冊中心。Dubbo 提供了 dubbo-registry-nacos
子專案,已經對 Nacos 進行適配,所以我們只要引入它,基本就完成了 Dubbo 和 Nacos 的整合,賊方便。
Nacos 致力於幫助您發現、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務後設資料及流量管理。
Nacos 幫助您更敏捷和容易地構建、交付和管理微服務平臺。 Nacos 是構建以“服務”為中心的現代應用架構 (例如微服務正規化、雲原生正規化) 的服務基礎設施。
還是老樣子,我們從「3. 註解配置」小節,複製出對應的三個 Maven 專案來進行改造,進行 Nacos 的整合。最終專案如下圖所示:
友情提示:本小節需要搭建一個 Nacos 服務,可以參考《Nacos 極簡入門》文章。
6.1 API
將「3. 註解配置」小節的 user-rpc-service-api-02
,複製出 user-rpc-service-api-03
,無需做任何改動。
6.2 Provider
將「3. 註解配置」小節的 user-rpc-service-provider-02
,複製出 user-rpc-service-provider-03
,接入 Nacos 作為註冊中心。改動點如下圖:
6.2.1 引入依賴
修改 pom.xml
檔案,額外引入 Nacos 相關的依賴如下:
<!-- 使用 Nacos 作為註冊中心 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.4.1</version>
</dependency>
6.2.2 配置檔案
修改 application.yaml
配置檔案,修改 dubbo.registry.address
配置項,設定 Nacos 作為註冊中心。完整配置如下:
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-provider # 應用名
# Dubbo 註冊中心配
registry:
address: nacos://127.0.0.1:8848 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 服務提供者協議配置
protocol:
port: -1 # 協議埠。使用 -1 表示隨機埠。
name: dubbo # 使用 `dubbo://` 協議。更多協議,可見 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文件
# Dubbo 服務提供者配置
provider:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.0
# 配置掃描 Dubbo 自定義的 @Service 註解,暴露成 Dubbo 服務提供者
scan:
base-packages: cn.iocoder.springboot.lab30.rpc.service
友情提示:艿艿本機搭建的 Nacos 服務啟動在預設的 8848 埠。
6.3 Consumer
將「3. 註解配置」小節的 user-rpc-service-consumer-02
,複製出 user-rpc-service-consumer-03
,接入 Nacos 作為註冊中心。改動點如下圖:
友情提示:整合的過程,和「6.2 Provider」一模一樣。
6.3.1 引入依賴
修改 pom.xml
檔案,額外引入 Nacos 相關的依賴如下:
<!-- 使用 Nacos 作為註冊中心 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.4.1</version>
</dependency>
6.3.2 配置檔案
修改 application.yaml
配置檔案,修改 dubbo.registry.address
配置項,設定 Nacos 作為註冊中心。完整配置如下:
# dubbo 配置項,對應 DubboConfigurationProperties 配置類
dubbo:
# Dubbo 應用配置
application:
name: user-service-consumer # 應用名
# Dubbo 註冊中心配置
registry:
address: nacos://127.0.0.1:8848 # 註冊中心地址。個鞥多註冊中心,可見 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文件。
# Dubbo 消費者配置
consumer:
timeout: 1000 # 【重要】遠端服務呼叫超時時間,單位:毫秒。預設為 1000 毫秒,胖友可以根據自己業務修改
UserRpcService:
version: 1.0.0
友情提示:艿艿本機搭建的 Nacos 服務啟動在預設的 8848 埠。
6.4 簡單測試
① 使用 ProviderApplication 啟動服務提供者。在 Nacos 控臺中,我們可以看到以 providers
為開頭的服務提供者,如下圖所示:
② 使用 ConsumerApplication 啟動服務消費者。在 Nacos 控臺中,我們可以看到以 consumers
為開頭的服務消費者,如下圖所示:
更多關於 Dubbo 整合 Nacos 作為註冊中心的內容,可以看看《Dubbo 文件 —— Nacos 註冊中心》。
7. 整合 Sentinel
示例程式碼對應倉庫:
lab-30-dubbo-annotations-sentinel
。
本小節我們來進行 Dubbo 和 Sentinel 的整合,使用 Sentinel 進行 Dubbo 的流量保護。Sentinel 提供了 sentinel-apache-dubbo-adapter
子專案,已經對 Dubbo 進行適配,所以我們只要引入它,基本就完成了 Dubbo 和 Sentinel 的整合,賊方便。
Sentinel 是阿里中介軟體團隊開源的,面向分散式服務架構的輕量級流量控制產品,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助使用者保護服務的穩定性。
還是老樣子,我們從「3. 註解配置」小節,複製出對應的三個 Maven 專案來進行改造,進行 Sentinel 的整合。最終專案如下圖所示:
友情提示:本小節需要搭建一個 Sentinel 服務,可以參考《Sentinel 極簡入門》文章。
7.1 API
將「3. 註解配置」小節的 user-rpc-service-api-02
,複製出 user-rpc-service-api-04
,無需做任何改動。
7.2 Provider
將「3. 註解配置」小節的 user-rpc-service-provider-02
,複製出 user-rpc-service-provider-03
,接入 Sentinel 實現服務消費者的流量控制。改動點如下圖:
7.2.1 引入依賴
修改 pom.xml
檔案,額外引入 Sentinel 相關的依賴如下:
<!-- Sentinel 核心庫 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制檯 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 對 Dubbo 的支援 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>1.7.1</version>
</dependency>
7.2.2 Sentinel 配置檔案
在 resources
目錄下,建立 Sentinel 自定義的sentinel.properties
配置檔案。內容如下:
csp.sentinel.dashboard.server=127.0.0.1:7070
csp.sentinel.dashboard.server
配置項,設定 Sentinel 控制檯地址。- 更多其它配置項,可見《Sentinel 官方文件 —— 啟動配置項》。
7.3 Consumer
將「2. 快速入門」小節的 user-rpc-service-consumer-02
,複製出 user-rpc-service-consumer-04
,接入 Sentinel 實現服務消費者的流量控制。改動點如下圖:
友情提示:整合的過程,和「7.2 Provider」一模一樣。
7.3.1 引入依賴
修改 pom.xml
檔案,額外引入 Sentinel 相關的依賴如下:
<!-- Sentinel 核心庫 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制檯 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 對 Dubbo 的支援 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>1.7.1</version>
</dependency>
7.3.2 Sentinel 配置檔案
在 resources
目錄下,建立 Sentinel 自定義的sentinel.properties
配置檔案。內容如下:
csp.sentinel.dashboard.server=127.0.0.1:7070
7.3.3 UserController
建立 UserController 類,增加呼叫 UserRpcService 服務的 HTTP API 介面。程式碼如下:
@RestController
@RequestMapping("/user")
public class UserController {
@Reference(version = "${dubbo.consumer.UserRpcService.version}")
private UserRpcService userRpcService;
@GetMapping("/get")
public UserDTO get(@RequestParam("id") Integer id) {
return userRpcService.get(id);
}
}
友情提示:注意,需要額外引入
spring-boot-starter-web
依賴。因為它不是主角,所以並沒有主動寫出來哈~
7.4 簡單測試
① 使用 ProviderApplication 啟動服務提供者。使用 ConsumerApplication 啟動服務消費者。
② 訪問服務消費者的 http://127.0.0.1:8080/user/get?id=1 介面,保證相關資源的初始化。
下面,我們來演示使用 Sentinel 對服務消費者的流量控制。
而 Sentinel 對服務提供者的流量控制是一樣的,胖友可以自己去嘗試。
③ 使用瀏覽器,訪問下 http://127.0.0.1:7070/ 地址,進入 Sentinel 控制檯。
然後,點選 Sentinel 控制檯的「簇點鏈路」選單,可以看到看到 Dubbo 服務消費者產生的 cn.iocoder.springboot.lab30.rpc.api.UserRpcService.UserRpcService:get(java.lang.Integer)
資源。如下圖所示:
點選 n.iocoder.springboot.lab30.rpc.api.UserRpcService.UserRpcService:get(java.lang.Integer)
資源所在列的「流控」按鈕,彈出「新增流控規則」。填寫流控規則,如下圖所示:
- 這裡,我們建立的是比較簡單的規則,僅允許該資源被每秒呼叫一次。
④ 使用瀏覽器,快速訪問 http://127.0.0.1:8080/user/get?id=1 介面兩次,會呼叫 UserService#get(Integer id)
方法兩次,會有一次被 Sentinel 流量控制而拒絕,返回結果如下圖所示:
因為預設的錯誤提示不是很友好,所以胖友可以自定義 SpringMVC 全域性錯誤處理器,對 Sentinel 的異常進行處理。感興趣的胖友,可以閱讀《芋道 Spring Boot SpringMVC 入門》文章的「5. 全域性異常處理」小節。
重要的友情提示:更多 Sentinel 的使用方式,胖友可以閱讀《芋道 Spring Boot 服務容錯 Sentinel 入門》文章。
7.5 DubboFallback
sentinel-apache-dubbo-adapter
支援配置全域性的 fallback 函式,可以在 Dubbo 服務被 Sentinel 限流/降級/負載保護的時候,進行相應的 fallback 處理。
- 我們只需要實現自定義的 DubboFallback 介面,並通過 DubboFallbackRegistry 進行註冊即可。
- 預設情況下,使用 DubboFallback 的 DefaultDubboFallback 實現類,它會將 BlockException 包裝成 SentinelRpcException 異常後丟擲。
另外,我們還可以配合 Dubbo 的 fallback 機制,來為降級的服務提供替代的實現。
現在,Dubbo 可以說從原本的 Java RPC 框架,演化成 Dubbo 生態體系,其周邊也越來越豐富。所以,讓我們一起來期望 《Dubbo 3.0 預覽版詳細解讀,關注非同步化和響應式程式設計》 。
? 無意中,發現 Dubbo 官方已經整理了 Dubbo 的整個生態體系,具體可以看看 Build production-ready microservices 頁面。咳咳咳,真特喵的齊全,完全學不動了。
另外,有一點需要提醒下,很多初學 Dubbo 的胖友,可能會犯跟艿艿一樣的錯誤,直接把原本的 Service 層,直接接入 Dubbo 框架,提供 Dubbo Service RPC 呼叫。其實這是不對的!具體的程式碼結構和專案的示例,可以看看 onemall/demo 專案。
因為本文僅僅是在 Spring Boot 下使用 Dubbo RPC 框架的入門文章,這裡在推薦一些不錯的內容:
- 《Dubbo 官方文件》 :還有比官方文件更香的東西麼?在國內的開源專案中,Dubbo 的文件質量,起碼 TOP10 吧。
- dubbo-samples 倉庫:提供了大量的示例,美滋滋。
- 《設計 RPC 介面時,你有考慮過這些嗎?》 :讓你更優雅的設計 RPC 介面。
- 《你的專案應該如何正確分層?》 :可以借鑑的專案分層。
相關文章
- Spring Boot入門Spring Boot
- Spring Boot系列(一):Spring Boot 入門篇Spring Boot
- Spring Boot HttpExchange 入門Spring BootHTTP
- spring boot快速入門Spring Boot
- Spring Boot Dubbo NacosSpring Boot
- Spring Boot入門(一):搭建Spring Boot專案Spring Boot
- Spring boot入門(一):快速搭建Spring boot專案Spring Boot
- Spring Boot (一)快速入門Spring Boot
- Spring Boot 2.0.1 入門教程Spring Boot
- Spring Boot整合hessian入門Spring Boot
- Spring Boot的Kafka入門Spring BootKafka
- Spring Boot 整合 Apache DubboSpring BootApache
- Spring Boot 整合 Spring Security 入門案例教程Spring Boot
- Spring Boot 2和JPA入門Spring Boot
- Spring Boot 中使用 thrift 入門Spring Boot
- Spring Boot 中使用 grpc 入門Spring BootRPC
- Spring boot入門(二):Spring boot整合MySql,Mybatis和PageHelper外掛Spring BootMySqlMyBatis
- GraphQL SPQR和Spring Boot入門 | baeldungSpring Boot
- spring-boot入門程式詳解Springboot
- Dubbo 入門
- RSocket入門:Spring Boot伺服器 -Spring.ioSpring Boot伺服器
- Spring Boot 2.0 WebFlux 教程 (一) | 入門篇Spring BootWebUX
- Spring Boot 最佳實踐(一)快速入門Spring Boot
- Spring Boot入門-快速搭建web專案Spring BootWeb
- Spring boot + Zookeeper + Dubbo學習筆記Springboot筆記
- 專題一之Spring Boot入門詳解Spring Boot
- Spring Boot GraphQL 實戰 01_快速入門Spring Boot
- 高效開發 Dubbo?用 Spring Boot 可得勁!Spring Boot
- Spring Boot入門(一):使用IDEA建立Spring Boot專案並使用yaml配置檔案Spring BootIdeaYAML
- Spring Boot 2 (十):Spring Boot 中的響應式程式設計和 WebFlux 入門Spring Boot程式設計WebUX
- # 8 快速入門 dubbo
- Spring Boot 2.x基礎教程:快速入門Spring Boot
- Spring Boot響應式客戶端WebClient入門Spring Boot客戶端Webclient
- Spring Boot -01- 快速入門篇(圖文教程)Spring Boot
- Spring Boot入門系列(二十)快速打造Restful API 介面Spring BootRESTAPI
- Spring Boot 2.x(十七):快速入門Elastic SearchSpring BootAST
- .NET程式設計師如何快入門Spring Boot程式設計師Spring Boot
- spring boot入門,看這篇文章就夠了Spring Boot