1. 程式人生 > >第一個 Dubbo 應用

第一個 Dubbo 應用

Java RMI 簡介

Java RMI (Remote Method Invocation)- 遠端方法呼叫,能夠讓客戶端像使用本地呼叫一樣呼叫服務端 Java 虛擬機器中的物件方法。RMI 是面嚮物件語言領域對 RPC (Remote Procedure Call)的完善,使用者無需依靠 IDL 的幫助來完成分散式呼叫,而是通過依賴介面這種更簡單自然的方式。

Java RMI 工作原理

一個典型的 RMI 呼叫如下圖所示:

  1. 服務端向 RMI 註冊服務繫結自己的地址,
  2. 客戶端通過 RMI 註冊服務獲取目標地址,
  3. 客戶端呼叫本地的 Stub 物件上的方法,和呼叫本地物件上的方法一致,
  4. 本地存根物件將呼叫資訊打包,通過網路傳送到服務端,
  5. 服務端的 Skeleton 物件收到網路請求之後,將呼叫資訊解包,
  6. 然後找到真正的服務物件發起呼叫,並將返回結果打包通過網路傳送回客戶端。

RMI Flow.png | centerRMI Flow.png | center

Java RMI 基本概念

Java RMI 是 Java 領域建立分散式應用的技術基石。後續的 EJB 技術,以及現代的分散式服務框架,其中的基本理念依舊是 Java RMI 的延續。在 RMI 呼叫中,有以下幾個核心的概念:

  1. 通過介面進行遠端呼叫
  2. 通過客戶端的 Stub 物件和服務端的 Skeleton 物件的幫助將遠端呼叫偽裝成本地呼叫
  3. 通過 RMI 註冊服務完成服務的註冊和發現

對於第一點,客戶端需要依賴介面,而服務端需要提供該介面的實現。

對於第二點,在 J2SE 1.5 版本之前需要通過 rmic 預先編譯好客戶端的 Stub 物件和服務端的 Skeleton 物件。在之後的版本中,不再需要事先生成 Stub 和 Skeleton 物件。

下面通過示例程式碼簡單的展示 RMI 中的服務註冊和發現

服務端的服務註冊

Hello obj = new HelloImpl(); // #1
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0); // #2
Registry registry = LocateRegistry.createRegistry(1099); // #3
registry.rebind("Hello", stub); // #4

說明:

  1. 初始化服務物件例項,
  2. 通過 UnicastRemoteObject.exportObject 生成可以與服務端通訊的 Stub 物件,
  3. 建立一個本地的 RMI 註冊服務,監聽埠為 1099。該註冊服務執行在服務端,也可以單獨啟動一個註冊服務的程序,
  4. 將 Stub 物件繫結到註冊服務上,這樣,客戶端可以通過 Hello 這個名字查詢到該遠端物件。

客戶端的服務發現

Registry registry = LocateRegistry.getRegistry(); // #1
Hello stub = (Hello) registry.lookup("Hello"); // #2
String response = stub.sayHello(); // #3

說明:

  1. 獲取註冊服務例項,在本例中,由於沒有傳入任何引數,假定要獲取的註冊服務例項部署在本機,並監聽在 1099 埠上,
  2. 從註冊服務中查詢服務名為 Hello 的遠端物件,
  3. 通過獲取的 Stub 物件發起一次 RMI 呼叫並獲得結果。

理解 RMI 的工作原理和基本概念,對掌握現代分散式服務框架很有幫助,建議進一步的閱讀 RMI 官方教材 [1]

Dubbo 基本概念

現代的分散式服務框架的基本概念與 RMI 是類似的,同樣是使用 Java 的 Interface 作為服務契約,通過註冊中心來完成服務的註冊和發現,遠端通訊的細節也是通過代理類來遮蔽。具體來說,Dubbo 在工作時有以下四個角色參與:

  1. 服務提供者 - 啟動時在指定埠上暴露服務,並將服務地址和埠註冊到註冊中心上
  2. 服務消費者 - 啟動時向註冊中心訂閱自己感興趣的服務,以便獲得服務提供方的地址列表
  3. 註冊中心 - 負責服務的註冊和發現,負責儲存服務提供方上報的地址資訊,並向服務消費方推送
  4. 監控中心 - 負責收集服務提供方和消費方的執行狀態,比如服務呼叫次數、延遲等,用於監控
  5. 執行容器 - 負責服務提供方的初始化、載入以及執行的生命週期管理

dubbo-architecture.png | left | 827x516dubbo-architecture.png | left | 827x516

部署階段

  • 服務提供者在指定埠暴露服務,並向註冊中心註冊服務資訊。
  • 服務消費者向註冊中心發起服務地址列表的訂閱。

執行階段

  • 註冊中心向服務消費者推送地址列表資訊。
  • 服務消費者收到地址列表後,從其中選取一個向目標服務發起呼叫。
  • 呼叫過程服務消費者和服務提供者的執行狀態上報給監控中心。

基於 API 的 Dubbo 應用

Dubbo 的應用一般都是通過 Spring 來組裝的。為了快速獲得一個可以工作的 Dubbo 應用,這裡的示例摒棄了複雜的配置,而改用面向 Dubbo API 的方式來構建服務提供者和消費者,另外,註冊中心和監控中心在本示例中也不需要安裝和配置。

在生產環境,Dubbo 的服務需要一個分散式的服務註冊中心與之配合,比如,ZooKeeper。為了方便開發,Dubbo 提供了直連[2]以及組播[3]兩種方式,從而避免額外搭建註冊中心的工作。在本例中,將使用組播的方式來完成服務的註冊和發現。

定義服務契約

public interface GreetingsService {
    String sayHi(String name); // #1
}

說明

  1. 定義了一個簡單的服務契約 GreetingsService,其中只有一個方法 sayHi 可供呼叫,入參是 String 型別,返回值也是 String 型別。

提供契約的實現

public class GreetingsServiceImpl implements GreetingsService { // #1
    @Override
    public String sayHi(String name) {
        return "hi, " + name; // #2
    }
}

說明

  1. 服務提供者需要實現服務契約 GreetingsService 介面。
  2. 該實現簡單的返回一個歡迎資訊,如果入參是 dubbo,則返回 hi, dubbo

實現 Dubbo 服務提供方

public class Application {
    public static void main(String[] args) throws IOException {
        ServiceConfig<GreetingsService> service = new ServiceConfig<>(); // #1
        service.setApplication(new ApplicationConfig("first-dubbo-provider")); // #2
        service.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234")); // #3
        service.setInterface(GreetingsService.class); // #4
        service.setRef(new GreetingsServiceImpl()); // #5
        service.export(); // #6
        System.in.read(); // #7
    }
}

說明

  1. 建立一個 ServiceConfig 的例項,泛型引數資訊是服務介面型別,即 GreetingsService
  2. 生成一個 AplicatonConfig 的例項,並將其裝配進 ServiceConfig
  3. 生成一個 RegistryConfig 例項,並將其裝配進 ServiceConfig,這裡使用的是組播方式,引數是 multicast://224.5.6.7:1234。合法的組播地址範圍為:224.0.0.0 - 239.255.255.255
  4. 將服務契約 GreetingsService 裝配進 ServiceConfig
  5. 將服務提供者提供的實現 GreetingsServiceImpl 的例項裝配進 ServiceConfig
  6. ServiceConfig 已經具備足夠的資訊,開始對外暴露服務,預設監聽埠是 20880
  7. 為了防止服務端退出,按任意鍵或者 ctrl-c 退出。

實現 Dubbo 服務呼叫方

public class Application {
    public static void main(String[] args) {
        ReferenceConfig<GreetingsService> reference = new ReferenceConfig<>(); // #1
        reference.setApplication(new ApplicationConfig("first-dubbo-client")); // #2
        reference.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234")); // #3
        reference.setInterface(GreetingsService.class); // #4
        GreetingsService greetingsService = reference.get(); // #5
        String message = greetingsService.sayHi("dubbo"); // #6
        System.out.println(message); // #7
    }
}

說明

  1. 建立一個 ReferenceConfig 的例項,同樣,泛型引數資訊是服務介面型別,即 GreetingService
  2. 生成一個 AplicatonConfig 的例項,並將其裝配進 ReferenceConfig
  3. 生成一個 RegistryConfig 例項,並將其裝配進 ReferenceConfig,注意這裡的組播地址資訊需要與服務提供方的相同。
  4. 將服務契約 GreetingsService 裝配進 ReferenceConfig
  5. 從 ReferenceConfig 中獲取到 GreetingService 的代理。
  6. 通過 GreetingService 的代理髮起遠端呼叫,傳入的引數為 dubbo
  7. 列印返回結果 hi, dubbo

執行

完整的示例在 https://github.com/dubbo/dubbo-samples/tree/master/dubbo-samples-api 上提供。在完整的示例中,由於配置了 exec-maven-plugin,可以很方便的在命令列下通過 maven 的方式執行。當然,您也可以在 IDE 裡直接執行,但是需要注意的是,由於使用了組播的方式來發現服務,執行時需要指定 -Djava.net.preferIPv4Stack=true

構建示例

通過以下的命令來同步示例程式碼並完成構建:

$ git clone https://github.com/dubbo/dubbo-samples.git
$ cd dubbo-samples/dubbo-samples-api/
$ mvn clean package
INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building dubbo-samples-api 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ dubbo-samples-api ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.182 s
[INFO] Finished at: 2018-05-28T14:56:08+08:00
[INFO] Final Memory: 20M/353M
[INFO] ------------------------------------------------------------------------

當看到 BUILD SUCCESS 的時候表明構建完成,下面就可以開始進入執行階段了。

執行服務端

通過執行以下的 maven 命令來啟動服務提供者:

$ mvn -Djava.net.preferIPv4Stack=true -Dexec.mainClass=com.alibaba.dubbo.samples.server.Application exec:java
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building dubbo-samples-api 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ dubbo-samples-api ---
log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
first-dubbo-provider is running.

當 first-dubbo-provider is running. 出現時,代表服務提供者已經啟動就緒,等待客戶端的呼叫。

執行客戶端

通過執行以下的 maven 命令來呼叫服務:

$ mvn -Djava.net.preferIPv4Stack=true -Dexec.mainClass=com.alibaba.dubbo.samples.client.Application exec:java
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building dubbo-samples-api 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ dubbo-samples-api ---
log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
hi, dubbo

可以看到, hi, dubbo 是從服務提供者返回的執行結果。

快速生成 Dubbo 應用

Dubbo 還提供了一個公共服務快速搭建基於 Spring Boot 的 Dubbo 應用。訪問 http://start.dubbo.io 並按照下圖所示來生成示例工程:

dubbo initializr.png | left | 827x835dubbo initializr.png | left | 827x835

說明

  1. 在 Group 中提供 maven groupId,預設值是 com.example
  2. 在 Artifact 中提供 maven artifactId,預設值是 demo
  3. 在 DubboServiceName 中提供服務名,預設值是 com.example.HelloService
  4. 在 DubboServiceVersion 中提供服務的版本,預設值是 1.0.0
  5. 在 Client/Server 中選取本次構建的工程是服務提供者 (Server) 還是服務消費者 (Client),預設值是 server
  6. 使用 embeddedZookeeper 作為服務註冊發現,預設為勾選。
  7. 是否啟用 qos 埠,預設為不勾選,如果勾選可以通過 22222 埠訪問。
  8. 點選 Generate Project 即可下載生成好的工程。

在本例中展示的是服務提供者,同樣的,通過在生成介面選取 client 來生成對應的服務消費者。

執行

用 IDE 開啟生成好的工程,可以發現應用是一個典型的 Spring Boot 應用。程式的入口如下所示:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		new EmbeddedZooKeeper(2181, false).start();  // #1
		SpringApplication.run(DemoApplication.class, args); // #2
	}
}

說明

  1. 在 2181 埠上啟動嵌入式 ZooKeeper
  2. 啟動 Spring Boot 上下文。

可以直接在 IDE 中執行,輸出結果如下:

   bash
2018-05-28 16:59:38.072  INFO 59943 --- [           main] a.b.d.c.e.WelcomeLogoApplicationListener : 

  ████████▄  ███    █▄  ▀█████████▄  ▀█████████▄   ▄██████▄  
  ███   ▀███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ███    ███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ███    ███ ███    ███  ▄███▄▄▄██▀   ▄███▄▄▄██▀  ███    ███ 
  ███    ███ ███    ███ ▀▀███▀▀▀██▄  ▀▀███▀▀▀██▄  ███    ███ 
  ███    ███ ███    ███   ███    ██▄   ███    ██▄ ███    ███ 
  ███   ▄███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ████████▀  ████████▀  ▄█████████▀  ▄█████████▀   ▀██████▀  


 :: Dubbo Spring Boot (v0.1.0) : https://github.com/dubbo/dubbo-spring-boot-project
 :: Dubbo (v2.0.1) : https://github.com/alibaba/dubbo
 :: Google group : http://groups.google.com/group/dubbo

2018-05-28 16:59:38.079  INFO 59943 --- [           main] e.OverrideDubboConfigApplicationListener : Dubbo Config was overridden by externalized configuration {dubbo.application.name=dubbo-demo-server, dubbo.application.qosAcceptForeignIp=false, dubbo.application.qosEnable=true, dubbo.application.qosPort=22222, dubbo.registry.address=zookeeper://localhost:2181?client=curator, dubbo.registry.id=my-registry, dubbo.scan.basePackages=com.example} #1

...

2018-05-28 16:59:39.624  INFO 59943 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.746 seconds (JVM running for 2.963)

說明

  1. 輸出中列印的以 dubbo. 開頭的配置資訊,定義在 main/resources/application.properties 中。

通過 Telnet 管理服務

生成工程的時候如果選擇了啟用 qos 的話,就可以通過 telnet 或者 nc 來管理服務、檢視服務狀態。

   bash
$ telnet localhost 22222
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
  ████████▄  ███    █▄  ▀█████████▄  ▀█████████▄   ▄██████▄  
  ███   ▀███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ███    ███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ███    ███ ███    ███  ▄███▄▄▄██▀   ▄███▄▄▄██▀  ███    ███ 
  ███    ███ ███    ███ ▀▀███▀▀▀██▄  ▀▀███▀▀▀██▄  ███    ███ 
  ███    ███ ███    ███   ███    ██▄   ███    ██▄ ███    ███ 
  ███   ▄███ ███    ███   ███    ███   ███    ███ ███    ███ 
  ████████▀  ████████▀  ▄█████████▀  ▄█████████▀   ▀██████▀  


dubbo>
dubbo>ls
As Provider side:
+------------------------------+---+
|     Provider Service Name    |PUB|
+------------------------------+---+
|com.example.HelloService:1.0.0| Y |
+------------------------------+---+
As Consumer side:
+---------------------+---+
|Consumer Service Name|NUM|
+---------------------+---+

目前 qos 支援以下幾個命令,更詳細的資訊請查閱官方文件[4]

  • ls:列出消費者、提供者資訊
  • online:上線服務
  • offline:下線服務
  • help:聯機幫助

總結

在本文中,從 RMI 開始,介紹了 Java 領域分散式呼叫的基本概念,也就是基於介面程式設計、通過代理將遠端呼叫偽裝成本地、通過註冊中心完成服務的註冊和發現。

然後為了簡單起見,使用簡單的組播註冊方式和直接面向 Dubbo API 程式設計的方式介紹瞭如何開發一個 Dubbo 的完整應用。深入的瞭解 ServiceConfig 和 ReferenceConfig 的用法,對於進一步的使用 Spring XML 配置、乃至 Spring Boot 的程式設計方式有這很大的幫助。

最後,簡單的介紹瞭如何通過 Dubbo 團隊提供的公共服務 start.dubbo.io 快速搭建基於 Spring Boot 的 Dubbo 應用,並通過 qos 來做 Dubbo 服務的簡單運維。

----轉自