【Dubbo】Dubbo 從入門到實戰——很詳細的一篇文章

一、爲什麼需要 dubbo

很多時候,其實我們使用這個技術的時候,可能都是因爲項目需要,所以,我們就用了,但是,至於爲什麼我們需要用到這個技術,可能自身並不是很瞭解的,但是,其實瞭解技術的來由及背景知識,對於理解一項技術還是有幫助的,那麼,dubbo是怎麼被提上日程的呢?

在互聯網的發展過程中,在以前,我們只需要一個服務器,將程序全部打包好就可以,但是,隨着流量的增大,常規的垂直應用架構已無法應對,所以,架構就發生了演變。

1. 單一應用架構

2. 應用和數據庫單獨部署

3. 應用和數據庫集羣部署

4. 數據庫壓力變大,讀寫分離

5. 使用緩存技術加快速度

6. 數據庫分庫分表

7. 應用分爲不同的類型拆分

發展到這個階段的時候,我們發現,應用與應用之間的關係已經十分的複雜了,就會出現以下幾個問題(以下摘錄於官網):

① 當服務越來越多時,服務 URL 配置管理變得非常困難,F5 硬件負載均衡器的單點壓力也越來越大。
② 當進一步發展,服務間依賴關係變得錯蹤複雜,甚至分不清哪個應用要在哪個應用之前啓動,架構師都不能完整的描述應用的架構關係。
③ 接着,服務的調用量越來越大,服務的容量問題就暴露出來,這個服務需要多少機器支撐?什麼時候該加機器?

爲了解決這由於架構的演變所產生的問題幾個問題,於是,dubbo 產生了。當然,解決這個問題的技術不止 dubbo 。

摘錄於官網

從上面 Dubbo 的服務治理圖我們就可以看到,Duboo 很好了解決了上面所出現的一些問題。

所以,當你的系統架構發展到了這種階段的時候,就需要考慮使用 Dubbo 了。

二、Dubbo 技術架構

我們已經非常清楚的知道爲什麼在我們的系統中需要 Dubbo 這項技術了,下面,我們接着嘮叨嘮叨 Dubbo 的架構。

首先,上一張圖(摘自官網)。

摘自官網

看到圖之後,可能你對上面的幾個概念還是一臉懵逼,無從下手,下面,帶你看看這幾個角色到底是什麼意思?

節點角色說明

節點 角色說明
Provider 暴露服務的服務提供方
Consumer 調用遠程服務的服務消費方
Registry 服務註冊與發現的註冊中心
Monitor 統計服務的調用次數和調用時間的監控中心
Container 服務運行容器

看了這幾個概念後似乎發現,其實 Dubbo 的架構也是很簡單的(其實現細節是複雜的),爲什麼這麼說呢,有沒有發現,其實很像生產者-消費者模型。只是在這種模型上,加上了註冊中心和監控中心,用於管理提供方提供的url,以及管理整個過程。

那麼,整個發佈-訂閱的過程就非常的簡單了。

  • 啓動容器,加載,運行服務提供者
  • 服務提供者在啓動時,在註冊中心發佈註冊自己提供的服務
  • 服務消費者在啓動時,在註冊中心訂閱自己所需的服務

如果考慮失敗或變更的情況,就需要考慮下面的過程。

  • 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
  • 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
  • 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。

通過這番講解,我相信 Dubbo 的架構我們也輕車熟路了,那就直接入手,開車吧。

三、Dubbo 開始入門

終於走到這一步了,寫到這裏停了大概一週的時間,主要原因還是最近項目太忙,趕着交差呢,今天希望能一鼓作氣,完完整整的寫完 dubbo 的基礎篇!

3.1 服務端

首先,我們先把服務端的接口寫好,因爲其實 dubbo 的作用簡單來說就是給消費端提供接口。

1. 接口定義

/**
 * xml方式服務提供者接口
 */
public interface ProviderService {

    String SayHello(String word);
}

這個接口非常簡單,只是包含一個 SayHello 的方法。

2. 定義實現類

/**
 * xml方式服務提供者實現類
 */
public class ProviderServiceImpl implements ProviderService{

    public String SayHello(String word) {
        return word;
    }
}

這樣我們就把我們的接口寫好了,那麼我們應該怎麼將我們的服務暴露出去呢?

3. 導入 maven 依賴

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ouyangsihai</groupId>
    <artifactId>dubbo-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.5</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.32.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.8.0</version>
        </dependency>

    </dependencies>
</project>

這裏使用的 dubbo 的版本是 2.6.6 ,需要注意的是,如果你只導入 dubbo 的包的時候是會報錯的,找不到 netty 和 curator 的依賴,所以,在這裏我們需要把這兩個的依賴加上,就不會報錯了。

另外,這裏我們使用 zookeeper 作爲註冊中心。

到目前爲止,dubbo 需要的環境就已經可以了,下面,我們就把上面剛剛定義的接口暴露出去。

4. 暴露接口(xml 配置方法)

首先,我們在我們項目的 resource 目錄下創建 META-INF.spring 包,然後再創建 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"
       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">

    <!--當前項目在整個分佈式架構裏面的唯一名稱,計算依賴關係的標籤-->
    <dubbo:application name="provider" owner="sihai">
        <dubbo:parameter key="qos.enable" value="true"/>
        <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
        <dubbo:parameter key="qos.port" value="55555"/>
    </dubbo:application>

    <dubbo:monitor protocol="registry"/>

    <!--dubbo這個服務所要暴露的服務地址所對應的註冊中心-->
    <!--<dubbo:registry address="N/A"/>-->
    <dubbo:registry address="N/A" />

    <!--當前服務發佈所依賴的協議;webserovice、Thrift、Hessain、http-->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService"/>

    <!--Bean bean定義-->
    <bean id="providerService" class="com.sihai.dubbo.provider.service.ProviderServiceImpl"/>

</beans>

① 上面的文件其實就是類似 spring 的配置文件,而且,dubbo 底層就是 spring。
② 節點:dubbo:application
就是整個項目在分佈式架構中的唯一名稱,可以在 name 屬性中配置,另外還可以配置 owner 字段,表示屬於誰。
下面的參數是可以不配置的,這裏配置是因爲出現了端口的衝突,所以配置。
③ 節點:dubbo:monitor
監控中心配置, 用於配置連接監控中心相關信息,可以不配置,不是必須的參數。
④ 節點:dubbo:registry
配置註冊中心的信息,比如,這裏我們可以配置 zookeeper 作爲我們的註冊中心。address 是註冊中心的地址,這裏我們配置的是 N/A 表示由 dubbo 自動分配地址。或者說是一種直連的方式,不通過註冊中心。
⑤ 節點:dubbo:protocol
服務發佈的時候 dubbo 依賴什麼協議,可以配置 dubbo、webserovice、Thrift、Hessain、http等協議。
⑥ 節點:dubbo:service
這個節點就是我們的重點了,當我們服務發佈的時候,我們就是通過這個配置將我們的服務發佈出去的。interface 是接口的包路徑,ref 是第 ⑦ 點配置的接口的 bean。
⑦ 最後,我們需要像配置 spring 的接口一樣,配置接口的 bean。

到這一步,關於服務端的配置就完成了,下面我們通過 main 方法將接口發佈出去。

5. 發佈接口

package com.sihai.dubbo.provider;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;
import com.alibaba.dubbo.container.Main;
import com.sihai.dubbo.provider.service.ProviderService;
import com.sihai.dubbo.provider.service.ProviderServiceImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

/**
 * xml方式啓動
 *
 */
public class App 
{
    public static void main( String[] args ) throws IOException {
        //加載xml配置文件啓動
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/provider.xml");
        context.start();
        System.in.read(); // 按任意鍵退出
    }
}

發佈接口非常簡單,因爲 dubbo 底層就是依賴 spring 的,所以,我們只需要通過 ClassPathXmlApplicationContext 拿到我們剛剛配置好的 xml ,然後調用 context.start() 方法就啓動了。

看到下面的截圖,就算是啓動成功了,接口也就發佈出去了。

你以爲到這裏就結束了了,並不是的,我們拿到 dubbo 暴露出去的 url分析分析。

6. 分析

dubbo 暴露的 url

dubbo://192.168.234.1:20880/com.sihai.dubbo.provider.service.ProviderService?anyhost=true&application=provider&bean.name=com.sihai.dubbo.provider.service.ProviderService&bind.ip=192.168.234.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.sihai.dubbo.provider.service.ProviderService&methods=SayHello&owner=sihai&pid=8412&qos.accept.foreign.ip=false&qos.enable=true&qos.port=55555&side=provider&timestamp=1562077289380

分析

① 首先,在形式上我們發現,其實這麼牛逼的 dubbo 也是用類似於 http 的協議發佈自己的服務的,只是這裏我們用的是 dubbo 協議
② dubbo://192.168.234.1:20880/com.sihai.dubbo.provider.service.ProviderService
上面這段鏈接就是 ? 之前的鏈接,構成:協議://ip:端口/接口。發現是不是也沒有什麼神祕的。
③ anyhost=true&application=provider&bean.name=com.sihai.dubbo.provider.service.ProviderService&bind.ip=192.168.234.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.sihai.dubbo.provider.service.ProviderService&methods=SayHello&owner=sihai&pid=8412&qos.accept.foreign.ip=false&qos.enable=true&qos.port=55555&side=provider&timestamp=1562077289380
? 之後的字符串,分析後你發現,這些都是剛剛在 provider.xml 中配置的字段,然後通過 & 拼接而成的,聞到了 http 的香味了嗎?

終於,dubbo 服務端入門了。下面我們看看拿到了 url 後,怎麼消費呢?

3.2 消費端

上面提到,我們在服務端提供的只是點對點的方式提供服務,並沒有使用註冊中心,所以,下面的配置也是會有一些不一樣的。

1. 消費端環境配置

首先,我們在消費端的 resource 下建立配置文件 consumer.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"
       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">

    <!--當前項目在整個分佈式架構裏面的唯一名稱,計算依賴關係的標籤-->
    <dubbo:application name="consumer" owner="sihai"/>

    <!--dubbo這個服務所要暴露的服務地址所對應的註冊中心-->
    <!--點對點的方式-->
    <dubbo:registry address="N/A" />
    <!--<dubbo:registry address="zookeeper://localhost:2181" check="false"/>-->

    <!--生成一個遠程服務的調用代理-->
    <!--點對點方式-->
    <dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"
                     url="dubbo://192.168.234.1:20880/com.sihai.dubbo.provider.service.ProviderService"/>

    <!--<dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>-->

</beans>

分析

① 發現這裏的 dubbo:application 和 dubbo:registry 是一致的。
② dubbo:reference :我們這裏採用點對點的方式,所以,需要配置在服務端暴露的 url 。

2. maven 依賴

和服務端一樣

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ouyangsihai</groupId>
    <artifactId>dubbo-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.ouyangsihai</groupId>
            <artifactId>dubbo-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.10</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.5</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.32.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.8.0</version>
        </dependency>
    </dependencies>
</project>

3. 調用服務

package com.sihai.dubbo.consumer;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.sihai.dubbo.provider.service.ProviderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

/**
 * xml的方式調用
 *
 */
public class App 
{
    public static void main( String[] args ) throws IOException {

        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
        context.start();
        ProviderService providerService = (ProviderService) context.getBean("providerService");
        String str = providerService.SayHello("hello");
        System.out.println(str);
        System.in.read();

    }
}

這裏和服務端的發佈如出一轍。


如此,我們就成功調用接口了。

四、加入 zookeeper 作爲註冊中心

在前面的案例中,我們沒有使用任何的註冊中心,而是用一種直連的方式進行的。但是,實際上很多時候,我們都是使用 dubbo + zookeeper 的方式,使用 zookeeper 作爲註冊中心,這裏,我們就介紹一下 zookeeper 作爲註冊中心的使用方法。

這裏,我們在前面的入門實例中進行改造。

4.1 服務端

在服務端中,我們只需要修改 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"
       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">

    <!--當前項目在整個分佈式架構裏面的唯一名稱,計算依賴關係的標籤-->
    <dubbo:application name="provider" owner="sihai">
        <dubbo:parameter key="qos.enable" value="true"/>
        <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
        <dubbo:parameter key="qos.port" value="55555"/>
    </dubbo:application>

    <dubbo:monitor protocol="registry"/>

    <!--dubbo這個服務所要暴露的服務地址所對應的註冊中心-->
    <!--<dubbo:registry address="N/A"/>-->
    <dubbo:registry address="zookeeper://localhost:2181" check="false"/>

    <!--當前服務發佈所依賴的協議;webserovice、Thrift、Hessain、http-->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService"/>

    <!--Bean bean定義-->
    <bean id="providerService" class="com.sihai.dubbo.provider.service.ProviderServiceImpl"/>

</beans>

重點關注這句話

<dubbo:registry address="zookeeper://localhost:2181" />

在 address 中,使用我們的 zookeeper 的地址。

如果是 zookeeper 集羣的話,使用下面的方式。

<dubbo:registry protocol="zookeeper" address="192.168.11.129:2181,192.168.11.137:2181,192.168.11.138:2181"/>

服務端的配置就好了,其他的跟 入門案例 一樣。

4.2 消費端

跟服務端一樣,在消費端,我們也只需要修改 consumer.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"
       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">

    <!--當前項目在整個分佈式架構裏面的唯一名稱,計算依賴關係的標籤-->
    <dubbo:application name="consumer" owner="sihai"/>

    <!--dubbo這個服務所要暴露的服務地址所對應的註冊中心-->
    <!--點對點的方式-->
    <!--<dubbo:registry address="N/A" />-->
    <dubbo:registry address="zookeeper://localhost:2181" check="false"/>

    <!--生成一個遠程服務的調用代理-->
    <!--點對點方式-->
    <!--<dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"
                     url="dubbo://192.168.234.1:20880/com.sihai.dubbo.provider.service.ProviderService"/>-->

    <dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>

</beans>

① 註冊中心配置跟服務端一樣。

<dubbo:registry address="zookeeper://localhost:2181"/>

② dubbo:reference
由於我們這裏使用 zookeeper 作爲註冊中心,所以,跟點對點的方式是不一樣的,這裏不再需要 dubbo 服務端提供的 url 了,只需要直接引用服務端提供的接口即可。

<dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>

好了,消費端也配置好了,這樣就可以使用修改的入門案例,重新啓動運行了。

同樣成功了。

這時候的區別在於,將 dubbo 發佈的 url 註冊到了 zookeeper,消費端從 zookeeper 消費,zookeeper 相當於一箇中介,給消費者提供服務。

你以爲這就完了?不,好戲纔剛剛開始呢。

五、多種配置方式

入門實例的時候,我們使用的是 xml 配置的方式,對 dubbo 的環境進行了配置,但是,官方還提供了其他的配置方式,這裏我們也一一分解。

5.1 API配置方式

這種方式其實官方是不太推薦的,官方推薦使用 xml 配置的方式,但是,在有的時候測試的時候,還是可以用的到的,另外,爲了保證完整性,這些內容還是有必要講講的。

首先還是回到服務端工程。

1. 服務端

這裏我們使用 api 的方式配置,所以,provider.xml 這個配置文件就暫時不需要了,我們只需要在上面的 AppApi 這個類中的 main 方法中用 api配置及啓動即可。

package com.sihai.dubbo.provider;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;
import com.sihai.dubbo.provider.service.ProviderService;
import com.sihai.dubbo.provider.service.ProviderServiceImpl;

import java.io.IOException;

/**
 * Api方式啓動
 * api的方式調用不需要其他的配置,只需要下面的代碼即可。
 * 但是需要注意,官方建議:
 * Api方式用於測試用例使用,推薦xml的方式
 */
public class AppApi
{
    public static void main( String[] args ) throws IOException {

        // 服務實現
        ProviderService providerService = new ProviderServiceImpl();

        // 當前應用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("provider");
        application.setOwner("sihai");

        // 連接註冊中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://localhost:2181");
//        registry.setUsername("aaa");
//        registry.setPassword("bbb");

        // 服務提供者協議配置
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(20880);
        //protocol.setThreads(200);

        // 注意:ServiceConfig爲重對象,內部封裝了與註冊中心的連接,以及開啓服務端口

        // 服務提供者暴露服務配置
        ServiceConfig<ProviderService> service = new ServiceConfig<ProviderService>(); // 此實例很重,封裝了與註冊中心的連接,請自行緩存,否則可能造成內存和連接泄漏
        service.setApplication(application);
        service.setRegistry(registry); // 多個註冊中心可以用setRegistries()
        service.setProtocol(protocol); // 多個協議可以用setProtocols()
        service.setInterface(ProviderService.class);
        service.setRef(providerService);
        service.setVersion("1.0.0");

        // 暴露及註冊服務
        service.export();
    }
}

分析

看到上面的代碼是不是雲裏霧裏,不要慌,我們通過對照 xml 的方式分析一下。

registry 的 xml 方式
<dubbo:registry protocol="zookeeper" address="localhost:2181"/>
API 的方式
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://localhost:2181");

dubbo:registry節點對應RegistryConfig ,xml 的屬性對應 API 方式用 set 方法就可以了。對比之下,你就會發現,如果 API 的方式不熟悉,可以對照 xml 配置方式就可以。

其他 API

org.apache.dubbo.config.ServiceConfig
org.apache.dubbo.config.ReferenceConfig
org.apache.dubbo.config.ProtocolConfig
org.apache.dubbo.config.RegistryConfig
org.apache.dubbo.config.MonitorConfig
org.apache.dubbo.config.ApplicationConfig
org.apache.dubbo.config.ModuleConfig
org.apache.dubbo.config.ProviderConfig
org.apache.dubbo.config.ConsumerConfig
org.apache.dubbo.config.MethodConfig
org.apache.dubbo.config.ArgumentConfig

更詳細的可以查看官方文檔:
http://dubbo.apache.org/zh-cn...

我們再看看我配置的消費端的 Api 方式。

2. 消費端

同樣,我們不需要 consumer.xml 配置文件了,只需要在 main 方法中啓動即可。

package com.sihai.dubbo.consumer;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.sihai.dubbo.provider.service.ProviderService;

/**
 * api的方式調用
 * api的方式調用不需要其他的配置,只需要下面的代碼即可。
 * 但是需要注意,官方建議:
 * Api方式用於測試用例使用,推薦xml的方式
 */
public class AppApi {

    public static void main(String[] args) {
        // 當前應用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("consumer");
        application.setOwner("sihai");

        // 連接註冊中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://localhost:2181");

        // 注意:ReferenceConfig爲重對象,內部封裝了與註冊中心的連接,以及與服務提供方的連接

        // 引用遠程服務
        ReferenceConfig<ProviderService> reference = new ReferenceConfig<ProviderService>(); // 此實例很重,封裝了與註冊中心的連接以及與提供者的連接,請自行緩存,否則可能造成內存和連接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多個註冊中心可以用setRegistries()
        reference.setInterface(ProviderService.class);

        // 和本地bean一樣使用xxxService
        ProviderService providerService = reference.get(); // 注意:此代理對象內部封裝了所有通訊細節,對象較重,請緩存複用
        providerService.SayHello("hello dubbo! I am sihai!");
    }
}

這部分的 API 配置的方式就到這了,注意:官方推薦 xml 的配置方法

5.2 註解配置方式

註解配置方式還是需要了解一下的,現在微服務都傾向於這種方式,這也是以後發展的趨勢,0 配置應該是這幾年的趨勢。

那麼如何對 dubbo 使用註解的方式呢?我們先看服務端。

1. 服務端

第一步:定義接口及實現類,在上面的截圖中的 annotation 包下

package com.sihai.dubbo.provider.service.annotation;

/**
 * 註解方式接口
 */
public interface ProviderServiceAnnotation {
    String SayHelloAnnotation(String word);
}
package com.sihai.dubbo.provider.service.annotation;

import com.alibaba.dubbo.config.annotation.Service;

/**
 * 註解方式實現類
 */
@Service(timeout = 5000)
public class ProviderServiceImplAnnotation implements ProviderServiceAnnotation{

    public String SayHelloAnnotation(String word) {
        return word;
    }
}

@Service

@Service 用來配置 Dubbo 的服務提供方。

第二步:組裝服務提供方。通過 Spring 中 Java Config 的技術(@Configuration)和 annotation 掃描(@EnableDubbo)來發現、組裝、並向外提供 Dubbo 的服務。

package com.sihai.dubbo.provider.configuration;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 註解方式配置
 */
@Configuration
@EnableDubbo(scanBasePackages = "com.sihai.dubbo.provider.service.annotation")
public class DubboConfiguration {

    @Bean // #1 服務提供者信息配置
    public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setTimeout(1000);
        return providerConfig;
    }

    @Bean // #2 分佈式應用信息配置
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-provider");
        return applicationConfig;
    }

    @Bean // #3 註冊中心信息配置
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setProtocol("zookeeper");
        registryConfig.setAddress("localhost");
        registryConfig.setPort(2181);
        return registryConfig;
    }

    @Bean // #4 使用協議配置,這裏使用 dubbo
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(20880);
        return protocolConfig;
    }
}

分析

  • 通過 @EnableDubbo 指定在com.sihai.dubbo.provider.service.annotation 下掃描所有標註有 @Service 的類
  • 通過 @Configuration 將 DubboConfiguration 中所有的 @Bean 通過 Java Config 的方式組裝出來並注入給 Dubbo 服務,也就是標註有 @Service 的類。這其中就包括了:

    • ProviderConfig:服務提供方配置
    • ApplicationConfig:應用配置
    • RegistryConfig:註冊中心配置
    • ProtocolConfig:協議配置

看起來很複雜,其實。。。

第三步:啓動服務

package com.sihai.dubbo.provider;

import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import com.sihai.dubbo.provider.configuration.DubboConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sun.applet.Main;

import java.io.IOException;

/**
 * 註解啓動方式
 */
public class AppAnnotation {

    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DubboConfiguration.class); 
        context.start();
        System.in.read(); 
    }
}

發現輸出下面信息就表示 success 了。

2. 消費端

同樣我們下看看消費端的工程,有一個感性認識。
圖片.png

第一步:引用服務

package com.sihai.dubbo.consumer.Annotation;

import com.alibaba.dubbo.config.annotation.Reference;
import com.sihai.dubbo.provider.service.annotation.ProviderServiceAnnotation;
import org.springframework.stereotype.Component;

/**
 * 註解方式的service
 */
@Component("annotatedConsumer")
public class ConsumerAnnotationService {

    @Reference
    private ProviderServiceAnnotation providerServiceAnnotation;

    public String doSayHello(String name) {
        return providerServiceAnnotation.SayHelloAnnotation(name);
    }
}

在 ConsumerAnnotationService 類中,通過 @Reference 引用服務端提供的類,然後通過方法調用這個類的方式,給消費端提供接口。
注意:如果這裏找不到 ProviderServiceAnnotation 類,請在服務端先把服務端工程用 Maven intall 一下,然後將服務端的依賴放到消費端的 pom 中。如下:

<dependency>
          <groupId>com.ouyangsihai</groupId>
            <artifactId>dubbo-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

第二步:組裝服務消費者
這一步和服務端是類似的,這裏就不在重複了。

package com.sihai.dubbo.consumer.configuration;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * 註解配置類
 */
@Configuration
@EnableDubbo(scanBasePackages = "com.sihai.dubbo.consumer.Annotation")
@ComponentScan(value = {"com.sihai.dubbo.consumer.Annotation"})
public class ConsumerConfiguration {
    @Bean // 應用配置
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        Map<String, String> stringStringMap = new HashMap<String, String>();
        stringStringMap.put("qos.enable","true");
        stringStringMap.put("qos.accept.foreign.ip","false");
        stringStringMap.put("qos.port","33333");
        applicationConfig.setParameters(stringStringMap);
        return applicationConfig;
    }

    @Bean // 服務消費者配置
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setTimeout(3000);
        return consumerConfig;
    }

    @Bean // 配置註冊中心
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setProtocol("zookeeper");
        registryConfig.setAddress("localhost");
        registryConfig.setPort(2181);
        return registryConfig;
    }
}

第三步:發起遠程調用

在 main 方法中通過啓動一個 Spring Context,從其中查找到組裝好的 Dubbo 的服務消費者,併發起一次遠程調用。

package com.sihai.dubbo.consumer;

import com.sihai.dubbo.consumer.Annotation.ConsumerAnnotationService;
import com.sihai.dubbo.consumer.configuration.ConsumerConfiguration;
import com.sihai.dubbo.provider.service.ProviderService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

/**
 * 註解方式啓動
 *
 */
public class AppAnnotation
{
    public static void main( String[] args ) throws IOException {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); 
        context.start(); // 啓動
        ConsumerAnnotationService consumerAnnotationService = context.getBean(ConsumerAnnotationService.class); 
        String hello = consumerAnnotationService.doSayHello("annotation"); // 調用方法
        System.out.println("result: " + hello); // 輸出結果

    }
}

結果

六、常用場景

在下面的講解中,都會是以 xml 配置的方式來講解的,這也是 dubbo 官方比較推薦的方式。以下的操作都是在服務端的 xml 配置文件和消費端的配置文件來講解的。

6.1 啓動時檢查

Dubbo 缺省會在啓動時檢查依賴的服務是否可用,不可用時會拋出異常,阻止 Spring 初始化完成,以便上線時,能及早發現問題,默認 `check="true"。

但是,有的時候,我們並不是都需要啓動時就檢查的,比如測試的時候,我們是需要更快速的啓動,所以,這種場景的時候,我們是需要關閉這個功能的。

下面,我們看看如何使用這個功能。

在服務端註冊的時候(客戶端註冊時同樣適用);

<dubbo:registry protocol="zookeeper" address="localhost:2181,localhost:2182,localhost:2183" check="false"/>

在客戶端引用服務端服務的時候;

<dubbo:reference check="false" id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>

就是這麼簡單,就是這麼強!

6.2 集羣容錯

dubbo 也是支持集羣容錯的,同時也有很多可選的方案,其中,默認的方案是 failover,也就是重試機制。

首先,我們先把所有的容錯機制都整理一遍,然後再看看使用。

集羣模式 說明 使用方法
Failover Cluster 失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。可通過 retries="2" 來設置重試次數(不含第一次)。 cluster="xxx" xxx:集羣模式名稱 ,例如cluster="failover"
Failfast Cluster 快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。  
Failsafe Cluster 失敗安全,出現異常時,直接忽略。  
Failback Cluster 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。  
Forking Cluster 並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。  
Broadcast Cluster 廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息。  

使用實例
在發佈服務或者引用服務的時候設置

<!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service cluster="failover" retries="2"
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService"/>
<dubbo:reference cluster="failover" retries="2" check="false" id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>

6.3 負載均衡

負載均衡想必是一個再熟悉不過的概念了,所以,dubbo 支持也是再正常不過了,這裏也總結一下 dubbo 支持的負載均衡的一些方案及使用方法。

負載均衡模式 說明 使用方法
Random LoadBalance 隨機 按權重設置隨機概率 <dubbo:service loadbalance="xxx"/> xxx:負載均衡方法
RoundRobin LoadBalance 輪詢 按公約後的權重設置輪詢比率。  
LeastActive LoadBalance 最少活躍調用數 相同活躍數的隨機,活躍數指調用前後計數差。  
ConsistentHash LoadBalance 一致性 Hash 相同參數的請求總是發到同一提供者。 當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。  

6.4 直連提供者

在開發及測試環境下,經常需要繞過註冊中心,只測試指定服務提供者,所以,這種情況下,我們只需要直接連接服務端的地即可,其實,這種方法在前面的講解已經使用到了,第一種講解的方式就是這種方式,因爲這種方式簡單。

使用

<dubbo:reference id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"
                     url="dubbo://192.168.234.1:20880/com.sihai.dubbo.provider.service.ProviderService"/>

說明:可以看到,只要在消費端在 dubbo:reference 節點使用 url 給出服務端的方法即可。

6.5 只訂閱

只訂閱就是隻能夠訂閱服務端的服務,而不能夠註冊。

引用官方的使用場景如下:

爲方便開發測試,經常會在線下共用一個所有服務可用的註冊中心,這時,如果一個正在開發中的服務提供者註冊,可能會影響消費者不能正常運行。
可以讓服務提供者開發方,只訂閱服務(開發的服務可能依賴其它服務),而不註冊正在開發的服務,通過直連測試正在開發的服務。
<dubbo:registry register="false" protocol="zookeeper" address="localhost:2181,localhost:2182,localhost:2183" check="false"/>

① 使用只訂閱方式

當在服務提供端使用 register="false" 的時候,我們使用下面的方式獲取服務端的服務;

<dubbo:reference cluster="failover" retries="2" check="false" id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService"/>

啓動信息
圖片.png
發現,這時候並不是向註冊中心 zookeeper 註冊,而只是做了發佈服務和啓動netty

② 不使用只訂閱方式

<dubbo:registry protocol="zookeeper" address="localhost:2181,localhost:2182,localhost:2183" check="false"/>

啓動信息

可以發現,這裏就向註冊中心 zookeeper 註冊了。

6.6 只註冊

只註冊正好跟前面的只訂閱相反,這個時候可以向註冊中心註冊,但是,消費端卻不能夠讀到服務。

應用場景

如果有兩個鏡像環境,兩個註冊中心,有一個服務只在其中一個註冊中心有部署,另一個註冊中心還沒來得及部署,而兩個註冊中心的其它應用都需要依賴此服務。這個時候,可以讓服務提供者方只註冊服務到另一註冊中心,而不從另一註冊中心訂閱服務。

使用說明

<dubbo:registry subscribe="false" address="localhost:2181"></dubbo:registry>

在服務端的 dubbo:registry 節點下使用 subscribe="false" 來聲明這個服務是隻註冊的服務。

這個時候消費端調用的時候是不能調用的。

6.7 多協議機制

在前面我們使用的協議都是 dubbo 協議,但是 dubbo 除了支持這種協議外還支持其他的協議,比如,rmi、hessian等,另外,而且還可以用多種協議同時暴露一種服務。

使用方法

① 一種接口使用一種協議

先聲明多種協議

 <!--當前服務發佈所依賴的協議;webserovice、Thrift、Hessain、http-->
    <dubbo:protocol name="dubbo" port="20880"/>
    <dubbo:protocol name="rmi" port="1099" />

然後在發佈接口的時候使用具體協議

<!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service cluster="failover" retries="2"
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService"/>
    <dubbo:service cluster="failover" retries="2"
                   interface="com.sihai.dubbo.provider.service.ProviderService"
                   ref="providerService" protocol="rmi"/>

在輸出日誌中,就可以找到rmi發佈的接口。

rmi://192.168.234.1:1099/com.sihai.dubbo.provider.service.ProviderService?anyhost=true&application=provider&bean.name=com.sihai.dubbo.provider.service.ProviderService&cluster=failover&dubbo=2.0.2&generic=false&interface=com.sihai.dubbo.provider.service.ProviderService&methods=SayHello&owner=sihai&pid=796&retries=2&side=provider&timestamp=1564281053185, dubbo version: 2.6.6, current host: 192.168.234.1

② 一種接口使用多種協議
聲明協議和上面的方式一樣,在發佈接口的時候有一點不一樣。

<dubbo:service cluster="failover" retries="2"
                   interface="com.sihai.dubbo.provider.service.ProviderService"
                   ref="providerService" protocol="rmi,dubbo"/>

說明:protocol屬性,可以用,隔開,使用多種協議。

6.8 多註冊中心

Dubbo 支持同一服務向多註冊中心同時註冊,或者不同服務分別註冊到不同的註冊中心上去,甚至可以同時引用註冊在不同註冊中心上的同名服務。

服務端多註冊中心發佈服務

一個服務可以在不同的註冊中心註冊,當一個註冊中心出現問題時,可以用其他的註冊中心。

註冊

<!--多註冊中心-->
    <dubbo:registry protocol="zookeeper" id="reg1" timeout="10000" address="localhost:2181"/>
    <dubbo:registry protocol="zookeeper" id="reg2" timeout="10000" address="localhost:2182"/>
    <dubbo:registry protocol="zookeeper" id="reg3" timeout="10000" address="localhost:2183"/>

發佈服務

<!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service cluster="failover" retries="2"
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService" registry="reg1"/>
    <dubbo:service cluster="failover" retries="2"
                   interface="com.sihai.dubbo.provider.service.ProviderService"
                   ref="providerService" protocol="rmi" registry="reg2"/>

說明:使用registry="reg2"指定該接口使用的註冊中心,同時也可以使用多個,用隔開,例如,registry="reg1,,reg2"

消費端多註冊中心引用服務

首先,先向不同註冊中心註冊;

<!--多註冊中心-->
    <dubbo:registry protocol="zookeeper" id="reg1" timeout="10000" address="localhost:2181"/>
    <dubbo:registry protocol="zookeeper" id="reg2" timeout="10000" address="localhost:2182"/>
    <dubbo:registry protocol="zookeeper" id="reg3" timeout="10000" address="localhost:2183"/>

其次,不同的消費端服務引用使用不同的註冊中心;

!--不同的服務使用不同的註冊中心-->
    <dubbo:reference cluster="failover" retries="2" check="false" id="providerService"
                     interface="com.sihai.dubbo.provider.service.ProviderService" registry="reg1"/>
    <dubbo:reference cluster="failover" retries="2" check="false" id="providerService2"
                     interface="com.sihai.dubbo.provider.service.ProviderService" registry="reg2"/>

說明:上面分別使用註冊中心1和註冊中心2。

6.9 多版本

不同的服務是有版本不同的,版本可以更新並且升級,同時,不同的版本之間是不可以調用的。

<!--服務發佈的配置,需要暴露的服務接口-->
    <dubbo:service cluster="failover" retries="2"
            interface="com.sihai.dubbo.provider.service.ProviderService"
            ref="providerService" registry="reg1" version="1.0.0"/>
    <dubbo:service cluster="failover" retries="2"
                   interface="com.sihai.dubbo.provider.service.ProviderService"
                   ref="providerService" protocol="rmi" registry="reg2" version="1.0.0"/>

加入了版本控制。

6.10 日誌管理

dubbo 也可以將日誌信息記錄或者保存到文件中的。

① 使用accesslog輸出到log4j

<dubbo:protocol accesslog="true" name="dubbo" port="20880"/>
    <dubbo:protocol accesslog="true" name="rmi" port="1099" />

② 輸出到文件

<dubbo:protocol accesslog="http://localhost/log.txt" name="dubbo" port="20880"/>
    <dubbo:protocol accesslog="http://localhost/log2.txt" name="rmi" port="1099" />

七、總結

這篇文章就到這裏了,主要講了一下幾個內容
1、爲什麼需要dubbo
2、dubbo架構簡析
3、dubbo入門
4、zookeeper註冊中心加入dubbo
5、dubbo多種配置方式(xml、api、註解)
6、常用場景介紹
下一篇文章,將講講源碼分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章