分佈式專題-分佈式服務治理02-Dubbo常用配置

前言

dubbo這部分主要以三個方面展開

  • 解開Dubbo的神祕面紗
  • Dubbo的常用配置
  • Dubbo的源碼分析

本節我們講第一個部分:Dubbo常用配置

Dubbo

dubbo作爲一款優秀的RPC框架,基礎功能便是遠程調用,除此之外,框架本身還是提供了很多分佈式解決方案,也就是我們說的服務治理,接下來我們具體依據不同的配置文件看一下服務治理的效果!

本小節使用的Demo文件說明,本節所有代碼詳見文末的Gitlab地址
在這裏插入圖片描述

多版本支持

設置不同版本的目的,就是要考慮到接口升級以後帶來的兼容問題。在Dubbo中配置不同版本的接口,會在Zookeeper地址中有多個協議url的體現,這裏我們演示一個DEMO具體內容如下

dubbo-server.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="hello-world-app"/>
    <!-- 使用multicast廣播註冊中心暴露服務地址 -->
    <dubbo:registry address="zookeeper://192.168.200.111:2181"/>
    <!-- 用dubbo協議在20880端口暴露服務 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 聲明需要暴露的服務接口 -->
    <dubbo:service interface="com.test.dubbo.IGpHello"
                   ref="demoService" protocol="dubbo" version="1.0.0"/>

   <dubbo:service interface="com.test.dubbo.IGpHello"
                   ref="demoService2" protocol="dubbo" version="1.0.1"/>

    <!-- 和本地bean一樣實現服務 -->
    <bean id="demoService" class="com.test.dubbo.GpHelloImpl" />

    <bean id="demoService2" class="com.test.dubbo.GpHelloImpl2"/>

</beans>

這裏我們在服務端的配置文件通過配置兩個接口的實現類,並且配置不同的版本號,接下來我們啓動:

    public static void main( String[] args )
    {
        com.alibaba.dubbo.container.Main.main(new String[]{"spring","log4j"});
    }

啓動成功!

接下來,我們根據要在客戶端根據不同的版本號,去接收不同的實現類!

dubbo-client.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="hello-world-app"  />
    <!-- 使用multicast廣播註冊中心暴露服務地址 -->
    <dubbo:registry id="zookeeper" address="zookeeper://192.168.200.111:2181" file="d:/dubbo-server" />

    <!-- 聲明需要暴露的服務接口 -->
    <dubbo:reference id="demoService"
                     interface="com.test.dubbo.IGpHello"
                     registry="zookeeper"
                     version="1.0.0"/>
</beans>

注意我們這裏使用的是版本號是1.0.0,接下來加載客戶端的配置文件,看下一下效果

  public static void main( String[] args ) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext
                (new String[] {"dubbo-client.xml"});
        context.start();
        // 獲取遠程服務代理
        IGpHello demoService = (IGpHello)context.getBean("demoService");
        // 執行遠程方法
        String hello = demoService.sayHello("world");
        // 顯示調用結果
        System.out.println( hello );
    }

在這裏插入圖片描述
現在客戶端切換version爲1.0.1,運行後效果爲:

在這裏插入圖片描述
接下來,我們看一下對應的dubbo服務節點上有什麼變化?
在這裏插入圖片描述
我們知道,dubbo所有的配置信息基於url傳遞,這就是版本號的效果展示~


dubbo://http192.168.200.1%3A20880%2Fcom.test.dubbo.IGpHello2%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.5.6%26generic%3Dfalse%26interface%3Dcom.test.dubbo.IGpHello%26methods%3DsayHello%26pid%3D14580%26revision%3D1.0.1%26side%3Dprovider%26timestamp%3D1582464353568%26version%3D1.0.1

dubbo://http192.168.200.1%3A20880%2Fcom.test.dubbo.IGpHello%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.5.6%26generic%3Dfalse%26interface%3Dcom.test.dubbo.IGpHello%26methods%3DsayHello%26pid%3D14580%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1582464350622%26version%3D1.0.0

主機綁定

在發佈一個Dubbo服務的時候,會生成一個dubbo://ip:port的協議地址,那麼這個IP是根據什麼生成的呢?大家可以在ServiceConfig.java代碼中找到如下代碼;可以發現,在生成綁定的主機的時候,會通過一層一層的判斷,直到獲取到合法的ip地址。

//step 1
NetUtils.isInvalidLocalHost(host), 從配置文件中獲取host
//step 2
host = InetAddress.getLocalHost().getHostAddress();
//step 3
Socket socket = new Socket();
try {
    SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
    socket.connect(addr, 1000);
    host = socket.getLocalAddress().getHostAddress();
    break;
} finally {
    try {
        socket.close();
    } catch (Throwable e) {}
}
//step 4 遍歷本地網卡,返回一個合理的ip地址
  public static String getLocalHost() {
        InetAddress address = getLocalAddress();
        return address == null ? "127.0.0.1" : address.getHostAddress();
    }

集羣容錯

什麼是容錯機制? 容錯機制指的是某種系統控制在一定範圍內的一種允許或包容犯錯情況的發生,舉個簡單例子,我們在電腦上運行一個程序,有時候會出現無響應的情況,然後系統會彈出一個提示框讓我們選擇,是立即結束還是繼續等待,然後根據我們的選擇執行對應的操作,這就是“容錯”。
在分佈式架構下,網絡、硬件、應用都可能發生故障,由於各個服務之間可能存在依賴關係,如果一條鏈路中的其中一個節點出現故障,將會導致雪崩效應。爲了減少某一個節點故障的影響範圍,所以我們才需要去構建容錯服務,來優雅的處理這種中斷的響應結果\

Dubbo提供了6種容錯機制,分別如下
1.failsafe 失敗安全,可以認爲是把錯誤吞掉(記錄日誌)
2.failover(默認) 重試其他服務器; retries(2)
3.failfast 快速失敗, 失敗以後立馬報錯
4.failback 失敗後自動恢復。
5.forking forks. 設置並行數
6.broadcast 廣播,任意一臺報錯,則執行的方法報錯
配置方式如下,通過cluster方式,配置指定的容錯方案

   <!-- 聲明需要暴露的服務接口 -->
    <dubbo:reference id="demoService"
                     interface="com.test.dubbo.IGpHello"
                     registry="zookeeper"
                     version="1.0.1"
                     cluster="failsafe"/>

服務降級

降級的目的是爲了保證核心服務可用。
降級可以有幾個層面的分類: 自動降級和人工降級; 按照功能可以分爲:讀服務降級和寫服務降級;
1.對一些非核心服務進行人工降級,在大促之前通過降級開關關閉哪些推薦內容、評價等對主流程沒有影響的功能
2.故障降級,比如調用的遠程服務掛了,網絡故障、或者RPC服務返回異常。 那麼可以直接降級,降級的方案比如設置默認值、採用兜底數據(系統推薦的行爲廣告掛了,可以提前準備靜態頁面做返回)等等
3.限流降級,在秒殺這種流量比較集中並且流量特別大的情況下,因爲突發訪問量特別大可能會導致系統支撐不了。這個時候可以採用限流來限制訪問量。當達到閥值時,後續的請求被降級,比如進入排隊頁面,比如跳轉到錯誤頁(活動太火爆,稍後重試等)

dubbo的降級方式: Mock
實現步驟
1.在client端創建一個TestMock類,實現對應IGpHello的接口(需要對哪個接口進行mock,就實現哪個),名稱必須以Mock結尾

public class TestMock implements IGpHello{

    @Override
    public String sayHello(String s) {

        return "系統繁忙:"+s;
    }
}

2.在client端的xml配置文件中,添加如下配置,增加一個mock屬性指向創建的TestMock

    <!-- 聲明需要暴露的服務接口 -->
    <dubbo:reference id="demoService"
                     interface="com.test.dubbo.IGpHello"
                     registry="zookeeper"
                     version="1.0.1"
                     mock="com.test.dubbo.TestMock" timeout="1"/>

3.模擬錯誤(設置timeout),模擬超時異常,運行測試代碼即可訪問到TestMock這個類。當服務端故障解除以後,調用過程將恢復正常
在這裏插入圖片描述

配置優先級別

以timeout爲例,顯示了配置的查找順序,其它retries, loadbalance等類似。
1.方法級優先,接口級次之,全局配置再次之。
2.如果級別一樣,則消費方優先,提供方次之。
其中,服務提供方配置,通過URL經由註冊中心傳遞給消費方。
建議由服務提供方設置超時,因爲一個方法需要執行多長時間,服務提供方更清楚,如果一個消費方同時引用多個服務,就不需要關心每個服務的超時設置。

Dubbo SPI和JAVA SPI的使用和對比

在Dubbo中,SPI是一個非常核心的機制,貫穿在幾乎所有的流程中。搞懂這塊內容,是接下來了解Dubbo更多源碼的關鍵因素。

Dubbo是基於Java原生SPI機制思想的一個改進,所以,先從JAVA SPI機制開始瞭解什麼是SPI以後再去學習Dubbo的SPI,就比較容易了

關於JAVA 的SPI機制

SPI全稱(service provider interface),是JDK內置的一種服務提供發現機制,目前市面上有很多框架都是用它來做服務的擴展發現,大家耳熟能詳的如JDBC、日誌框架都有用到;
簡單來說,它是一種動態替換髮現的機制。舉個簡單的例子,如果我們定義了一個規範,需要第三方廠商去實現,那麼對於我們應用方來說,只需要集成對應廠商的插件,既可以完成對應規範的實現機制。 形成一種插拔式的擴展手段。

實現一個SPI機制

實現的代碼的流程圖如下~
在這裏插入圖片描述

SPI規範總結

實現SPI,就需要按照SPI本身定義的規範來進行配置,SPI規範如下
1.需要在classpath下創建一個目錄,該目錄命名必須是:META-INF/services
2.在該目錄下創建一個properties文件,該文件需要滿足以下幾個條件
a)文件名必須是擴展的接口的全路徑名稱
b)文件內部描述的是該擴展接口的所有實現類
c)文件的編碼格式是UTF-8
3.通過java.util.ServiceLoader的加載機制來發現

SPI的實際應用

SPI在很多地方有應用,大家可以看看最常用的java.sql.Driver驅動。JDK官方提供了java.sql.Driver這個驅動擴展點,但是你們並沒有看到JDK中有對應的Driver實現。 那在哪裏實現呢?
以連接Mysql爲例,我們需要添加mysql-connector-java依賴。然後,你們可以在這個jar包中找到SPI的配置信息。如下圖,所以java.sql.Driver由各個數據庫廠商自行實現。這就是SPI的實際應用。當然除了這個意外,大家在spring的包中也可以看到相應的痕跡

在這裏插入圖片描述
demo演示
在這裏插入圖片描述
DataBaseDriver:

public interface DataBaseDriver {

    String connect(String hospt);
}

MysqlDriver:

public class MysqlDriver implements DataBaseDriver{

    @Override
    public String connect(String s) {
        return "begin build Mysql connection";
    }
}

對應的配置文件:
在這裏插入圖片描述

OracleDriver:

public class OracleDriver implements DataBaseDriver{

    @Override
    public String connect(String s) {
        return "Build connection With Oracle:"+s;
    }
}

對應的配置文件:
在這裏插入圖片描述
測試:
在dubbo-server端
首先引入我們自定義的兩個jar包:

在這裏插入圖片描述
開始測試:

    public static void main(String[] args) {
        ServiceLoader<DataBaseDriver> serviceLoader=
                ServiceLoader.load(DataBaseDriver.class);

        for(DataBaseDriver driver:serviceLoader){
            System.out.println(driver.connect("localhost"));
        }
    }

在這裏插入圖片描述
這就是java spi~
SPI的缺點

  1. JDK標準的SPI會一次性加載實例化擴展點的所有實現,什麼意思呢?就是如果你在META-INF/service下的文件裏面加了N個實現類,那麼JDK啓動的時候都會一次性全部加載。那麼如果有的擴展點實現初始化很耗時或者如果有些實現類並沒有用到,那麼會很浪費資源
  2. 如果擴展點加載失敗,會導致調用方報錯,而且這個錯誤很難定位到是這個原因

Dubbo優化後的SPI實現

基於Dubbo提供的SPI規範實現自己的擴展
在瞭解Dubbo的SPI機制之前,先通過一段代碼初步瞭解Dubbo的實現方式,這樣,我們就能夠形成一個對比,得到這兩種實現方式的差異

Dubbo的SPI機制規範
大部分的思想都是和SPI是一樣,只是下面兩個地方有差異。

  1. 需要在resource目錄下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,並基於SPI接口去創建一個文件
  2. 文件名稱和接口名稱保持一致,文件內容和SPI有差異,內容是KEY對應Value

demo演示
我們自定義dubbo的協議protocol
在客戶端我們寫一個自定義protocol來實現protocol,爲了演示方便,我這裏以改變協議端口號爲示例:

public class DefineProtocol implements Protocol {

    @Override
    public int getDefaultPort() {
        return 8888;
    }

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return null;
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return null;
    }

    @Override
    public void destroy() {

    }
}

接下來按照dubbo的規範,定義key-value形式的配置文件
在這裏插入圖片描述
客戶端測試類:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext
                (new String[]{"dubbo-client.xml"});
        context.start();
        // 獲取遠程服務代理
        IGpHello demoService = (IGpHello) context.getBean("demoService");
        // 執行遠程方法
        String hello = demoService.sayHello("world");
        // 顯示調用結果
        System.out.println(hello);

        Protocol myProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");

        System.out.println(myProtocol.getDefaultPort());

    }

啓動服務端與客戶端:
在這裏插入圖片描述
端口改變,說明通過自定義dubbo的SPI沒有問題~

後記

本節demo地址:dubbo-config

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