1. 程式人生 > >高效能Java RPC框架Dubbo與zookeeper的使用

高效能Java RPC框架Dubbo與zookeeper的使用

一. 什麼是RPC

1. RPC 協議(Remote Procedure Call Protocol)

遠端過程呼叫協議,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC 協議假定某些傳輸協議的存在,如 TCP 或 UDP,為通訊程式之間攜帶資訊資料。在 OSI 網路通訊模型中,RPC 跨越了傳輸層和應用層。RPC 使得開發包括網路分散式程式在內的應用程式更加容易。 RPC 採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機呼叫程序傳送一個有程序引數的呼叫資訊到服務程序,然後等待應答資訊。在伺服器端,程序保持睡眠狀態直到呼叫資訊到達為止。當一個呼叫資訊到達,伺服器獲得程序引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程序接收答覆資訊,獲得程序結果,然後呼叫執行繼續進行。

2. RPC 框架

在單機時代一臺電腦執行多個程序,程序之間無法通訊,顯然這會浪費很多資源,因此後來出現 IPC(Inter-process communication: 單機中執行的程序之間的相互通訊 ),這樣就能允許程序之間進行通訊,比如在一臺計算機中的 A 程序寫了一個吃飯的方法,那在以前如果在 B 程序中也要有一個吃飯的方法,必須要在 B 程序中進行建立,但有了 RPC 後 B 只需要呼叫 A 程序的程式即可完成,再到後來網路時代的出現, 大家電腦都連起來,這時可不可以呼叫其他電腦上的程序呢,當然可以,這樣 RPC 框架就出現了。 嚴格意義上來講:Unix的生態系統中 RPC 可以在同一臺電腦上不同程序進行,也可以在不同電腦上進行;而在windows 裡面同一臺電腦上不同程序間的通訊還可以採用 LPC(本地訪問)。綜上:RPC 或 LPC是上層建築,IPC 是底層基礎。 RPC 框架有很多:比如 Thrift、dubbo、grpc 等。

3. RPC 與 HTTP 、TCP 、UDP 、Socket 的區別

TCP/UDP: 都是傳輸協議,主要區別是 tcp 協議連線需要 3 次握手,斷開需要四次揮手,是通過來傳輸的,就是確定連線後,一直髮送資訊,傳完後斷開。udp 不需要進行連線,直接把資訊封裝成多個報文,直接傳送。所以 udp 的速度更快寫,但是不保證資料的完整性HTTP: 超文字傳輸協議是一種應用層協議,建立在 TCP 協議之上 Socket: 是在應用程式層面上對 TCP/IP 協議的封裝和應用。其實是一個呼叫介面,方便程式設計師使用 TCP/IP 協議棧而已。程式設計師通過 socket 來使用 tcp/ip 協議。但是 socket 並不是一定要使用 tcp/ip 協議,Socket 程式設計介面在設計的時候,就希望也能適應其他的網路協議。 小結:

我們把網路傳輸類比於一條公路,那 TCP/UDP 就是貨車,HTTP 就是貨物,而 socket就是發動機。 RPC 是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。所以 RPC 的實現可以通過不同的協議去實現比如可以使 http、RMI 等。

4. RPC 的執行流程

首先, 要解決通訊的問題,主要是通過在客戶端和伺服器之間建立 TCP 連線,遠端過程呼叫的所有交換的資料都在這個連線裡傳輸。連線可以是按需連線,呼叫結束後就斷掉,也可以是長連線,多個遠端過程呼叫共享同一個連線。 第二, 要解決定址的問題,也就是說,A 伺服器上的應用怎麼告訴底層的 RPC 框架,如何連線到 B 伺服器(如主機或 IP 地址)以及特定的埠,方法的名稱名稱是什麼,這樣才能完成呼叫。比如基於Web服務協議棧的RPC,就要提供一個endpoint URI,或者是從UDDI(一種目錄服務,通過該目錄服務進行服務註冊與搜尋)服務上查詢。如果是 RMI 呼叫的話,還需要一個 RMI Registry 來註冊服務的地址。 第三, 當 A 伺服器上的應用發起遠端過程呼叫時,方法的引數需要通過底層的網路協議如 TCP 傳遞到 B 伺服器,由於網路協議是基於二進位制的,記憶體中的引數的值要序列化成二進位制的形式,也就是序列化(Serialize)或編組(marshal),通過定址和傳輸將序列化的二進位制傳送給 B 伺服器。 第四, B 伺服器收到請求後,需要對引數進行反序列化(序列化的逆操作),恢復為記憶體中的表達方式,然後找到對應的方法(定址的一部分)進行本地呼叫,然後得到返回值。 第五, 返回值還要傳送回伺服器 A 上的應用,也要經過序列化的方式傳送,伺服器 A 接到後,再反序列化,恢復為記憶體中的表達方式,交給 A 伺服器上的應用 在這裡插入圖片描述 JAVAEE 裡面的 stub 是為遮蔽客戶呼叫遠端主機上的物件,必須提供某種方式來模擬本地物件,這種本地物件稱為存根(stub), 存根負責接收本地方法呼叫,並將它們委派給各自的具體實現物件 Skeleton:伺服器的骨架

5. RPC 基於RMI的簡單實現

5-1 首先建一個多模組的maven專案, 結構如下:

父工程包含三個子模組

5-2 rpc_rmi_api模組實現, 就是提供呼叫的介面, 結構圖如下:

在這裡插入圖片描述

User類:(必須實現序列化介面)
public class User implements Serializable {

    private static final long serialVersionUID = 6290816946970912418L;
    private Integer id;
    private String userName;
    private String userPwd;

    public User(Integer id, String userName, String userPwd) {
        this.id = id;
        this.userName = userName;
        this.userPwd = userPwd;
    }

    public User(){

    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
}
UserService介面:(相關規定看註釋)
public interface UserService extends Remote { //規定繼承Remote父類

    public User queryUserById(Integer userId) throws RemoteException; //規定丟擲異常
}
5-3 rpc_rmi_provider的模組實現,結構圖如下:(主要提供服務)

在這裡插入圖片描述

UserServiceImp類實現: (要繼承UnicastRemoteObject 類,規定的)
public class UserServiceImp extends UnicastRemoteObject implements UserService {

    private Map<Integer, User> users = new HashMap<Integer, User>();

    public UserServiceImp() throws RemoteException{

        users.put(1,new User(1,"admin","123456"));
        users.put(2,new User(2,"shsxt","123456"));

    }

    @Override
    public User queryUserById(Integer userId) throws RemoteException {
        return users.get(userId);
    }
}
Publisher類的實現:(釋出服務端)
public class Publisher {
    
    public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
        //指定服務埠
        LocateRegistry.createRegistry(8989);
        //釋出服務
        Naming.bind("rmi://192.168.1.158:8989/userService", new UserServiceImp());
        
        System.out.println("成功釋出服務!!!");
    }
}
5-4 rpc_rmi_consumer結構圖如下:(客戶端的呼叫)

在這裡插入圖片描述

App 類的實現:(呼叫服務端)
public class App {
    public static void main( String[] args ) throws RemoteException, NotBoundException, MalformedURLException {
        UserService userService = (UserService) Naming.lookup("rmi://192.168.1.158:8989/userService");
        System.out.println(userService.queryUserById(2));
    }
}

結果: 在這裡插入圖片描述

二. Dubbo框架

1. 框架基本概念

A high performance Java RPC framework ----一個高效的遠端呼叫框架 在這裡插入圖片描述 Dubbo 是由阿里巴巴開源的一個高效能、基於 Java 開源的遠端呼叫框架。正如在許多RPC 系統中一樣,dubbo 是基於定義服務的概念,指定可以通過引數和返回型別遠端呼叫的方法。在伺服器端,伺服器實現這個介面,並執行一個 dubbo 伺服器來處理客戶端呼叫。在客戶端,客戶機有一個存根,它提供與伺服器相同的方法。 dubbo的架構圖: 在這裡插入圖片描述 Dubbo 提供三個核心功能:基於介面的遠端呼叫、容錯和負載均衡,以及服務的自動注 冊與發現。Dubbo 框架廣泛的在阿里巴巴內部使用,以及京東、噹噹、去哪兒、考拉等都在 使用。

2. Dubbo快速入門

這裡也是從實戰出發,搭建一個簡單的遠端呼叫小例項, 首先也是要建立一個maven的多模組專案, 結果圖如下: 在這裡插入圖片描述 下面來一個模組一個模組的列出來:

2-1 dubbo_api 模組的結構圖如下:(也是提供方法呼叫介面)

在這裡插入圖片描述 大家不用像我建的那麼深,我的錯誤; 裡面的User與UserService與上面的rmi一樣;

2-2 dubbo_provider的結構圖如下:

在這裡插入圖片描述

UserServiceImp類的實現:(與上面不一樣的是不用繼承那個鬼類啦!)
@Service
public class UserServiceImp implements UserService {

    private Map<Integer, User> users = new HashMap<>();

    public UserServiceImp(){
        users.put(1,new User(1,"admin","123456"));
    }

    @Override
    public User queryUserById(Integer userId) {
        return users.get(userId);
    }
}
Publisher類的實現,與上面完全不同:
public class Publisher {

    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        System.out.println("服務釋出成功!!!");
        System.in.read(); //按任意鍵退出
    }
}
配置檔案provider.xml的實現
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.shsxt.provider"/>

    <!--
        dubbo 環境配置
    -->

    <!--應用名稱-->
    <dubbo:application name="dubbo_provider"></dubbo:application>

    <!--註冊中心配置:廣播地址-->
    <dubbo:registry address="multicast://224.5.6.7:1234"></dubbo:registry>

    <!--指定公佈的服務使用的協議與埠號-->
    <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>

    <!--配置公佈的服務-->
    <dubbo:service interface="com.shsxt.api.service.UserService" ref="userServiceImp"></dubbo:service>

</beans>
2-3 dubbo_consumer 模組買的實現, 結構圖如下:

在這裡插入圖片描述

UserController類的實現
@Controller
public class UserController {
    @Resource
    private UserService userService;
    
    public User queryUserById(Integer userId){
        return userService.queryUserById(userId);
    }
}
App類的實現
public class App {
    public static void main( String[] args ) {

        ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
        UserController userController = (UserController) context.getBean("userController");
        
        System.out.println(userController.queryUserById(1));
        
    }
}
配置檔案consumer.xml的程式碼(注意與provider.xml比較一下區別)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.shsxt.consumer"/>

    <!--
       dubbo 環境配置
    -->

    <!--配置應用名稱-->
    <dubbo:application name="dubbo_consumer"></dubbo:application>

    <!--指定註冊中心-->
   <dubbo:registry address="multicast://224.5.6.7:1234"></dubbo:registry>
 
    <!--配置訂閱的服務列表-->
    <dubbo:reference id="userService" interface="com.shsxt.api.service.UserService"></dubbo:reference>

</beans>
2-4 最後在主專案的pom.xml中加入下面依賴:(主專案中有單元測試,其他三個子模組的單元測試可以刪除掉)
 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--引入dubbo依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.6</version>
        </dependency>
 </dependencies>

三. 幾種重要配置的介紹

1. dubbo:application

	標籤					目的										介紹
dubbo:service	     服務出口	  		 用於匯出服務,定義服務元資料,使用多個協議匯出服務,向多個登錄檔註冊服務
dubbo:reference	 	 服務參考	 		 用於建立遠端代理,訂閱多個登錄檔
dubbo:protocol	     協議配置	  		 在供應商方面配置服務協議,消費者方面如下。
dubbo:application	 應用程式配置	 		 適用於提供者和消費者。
dubbo:module	     模組配置    	  	 可選的。
dubbo:registry		 註冊中心			 登錄檔資訊:地址,協議等
dubbo:monitor		 監控中心			 監控資訊:地址,地址等。可選。
dubbo:provider		 提供商的預設配置		 ServiceConfigs的預設配置。可選的。
dubbo:consumer	 	 消費者的預設配置	 	 ReferenceConfigs的預設配置。可選的。
dubbo:method		 方法級別配置			 ServiceConfig和ReferenceConfig的方法級別配置。
dubbo:argument	 	 引數配置			 用於指定方法引數配置。

2. dubbo:registry

註冊中⼼配置。對應的配置類: com.alibaba.dubbo.config.RegistryConfig。同時如果有多個不 同的註冊 中⼼, 可以聲 明多個 dubbo:registry 標籤 ,並在 dubbo:servicedubbo:reference 的 registry 屬性指定使⽤的註冊⼼。

3. dubbo:protocol

服務提供者協議配置。對應的配置類:com.alibaba.dubbo.config.ProtocolConfig,同時, 如果需要⽀持多協議, 可以宣告多個 dubbo:protocol 標籤, 並在 dubbo:service 中通過protocol 屬性指定使⽤的協議

4. dubbo:service

服務提供者暴露服務配置。對應的配置類:com.alibaba.dubbo.config.ServiceConfig

5. dubbo:reference

服務消費者引⽤服務配置。對應的配置類: com.alibaba.dubbo.config.ReferenceConfig

四. zookeeper安裝與註冊中心配置

在這裡插入圖片描述 上面為zookeeper的主要工作結構圖

流程說明:
  • 服務提供者啟動時: 向/dubbo/com.foo.BarService/providers 目錄下寫入自己的 URL 地址
  • 服務消費者啟動時: 訂閱 /dubbo/com.foo.BarService/providers 目錄下的提供者 URL 地址。並向 /dubbo/com.foo.BarService/consumers 目錄下寫入自己的URL 地址
  • 監控中心啟動時: 訂閱 /dubbo/com.foo.BarService 目錄下的所有提供者和消費者 URL 地址。
支援以下功能:
  • 當提供者出現斷電等異常停機時,註冊中心能自動刪除提供者資訊
  • 當註冊中心重啟時,能自動恢復註冊資料,以及訂閱請求
  • 當會話過期時,能自動恢復註冊資料,以及訂閱請求
  • 當設定 <dubbo:registry check=“false” /> 時,記錄失敗註冊和訂閱請求,後臺定時重試
  • 可 通 過 <dubbo:registry username=“admin” password=“1234” /> 設 置zookeeper 登入資訊
  • 可通過 <dubbo:registry group=“dubbo” /> 設定 zookeeper 的根節點,不設定將使用無根樹
  • 支援 * 號萬用字元 <dubbo:reference group="" version="" /> ,可訂閱服務的所有分組和所有版本的提供者

1. zookeeper的下載及安裝(linux環境)

為啥是linux呢? 簡單+不卡啊!

1-1 下載
在你認為合適的位置建一個資料夾: mkdir + 資料夾名
進入該目錄進行下載操作:  wget  http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
1-2 解壓
tar  -zvxf  + 你要解壓的壓縮包

解壓完成後的目錄結構 在這裡插入圖片描述

1-3 啟動前的熱身運動

如果你直接去bin目錄啟動zookeeper伺服器的話會報錯, 錯誤為:不能在conf中找到zoo.cfg這個配置檔案, 因為一開始conf裡面只有一個zoo_sample_cfg , 所以我們先在conf目錄下賦值一份, 名為zoo.cfg

cp  zoo_sample_cfg  zoo.cfg

在這裡插入圖片描述

用vim命令開啟zoo.cfg檔案,  vim zoo.cfg  內容如下:

在這裡插入圖片描述 每個配置對應的解釋詳細也能看懂,就不介紹了,可以不用改就可以跑去bin目錄下啟動啦!

1-4 正式啟動(bin目錄下)
 ./zkServer.sh start  (注意是要加start的) 啟動服務端
./zkCli.sh  為啟動客戶端

2. 對應的java配置

2-1 在 provider和consumer 中增加 zookeeper 客戶端 jar 包依賴:
<!--zookeeper配置-->
<dependency>
	<groupId>org.apache.zookeeper</groupId>
	<artifactId>zookeeper</artifactId>
	<version>3.3.3</version>
</dependency>
<!--zkclient客戶端-->
<dependency>
	<groupId>com.github.sgroschupf</groupId>
	<artifactId>zkclient</artifactId>
	<version>0.1</version>
</dependency>

上面使用了zkclient客戶端連線,除了這個還可以使用curator客戶端連線, 相應的依賴為:

<dependency>
	<groupId>com.netflix.curator</groupId>
	<artifactId>curator-framework</artifactId>
	<version>1.1.10</version>
</dependency>
2-2 Zookeeper 單機配置

就是改對應的provider與consumer模組的xml配置即可

<dubbo:registry address="zookeeper://10.20.153.10:2181" />
<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181" />
2-3 Zookeeper 叢集配置

不同的ip用逗號隔開

<dubbo:registry address="zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181" />
<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181" />
2-4 同一 Zookeeper分成多組註冊中心
<dubbo:registry id="chinaRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="china" />
<dubbo:registry id="intlRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="intl" />

我們上面的為單機版的就行,然後啟動java裡面的服務端與客戶程式的程式碼就行了, 因為zookeeper管理dubbo為樹形結構,使用可以檢視註冊中心的服務情況, 如下用ls 命令檢視就行, 檢視根節點為:

ls /dubbo  (記得前面加/) 想檢視哪一級的進哪一級檢視就行

在這裡插入圖片描述