Java-分布式框架-Dubbo-3

一、分布式項目開發(fā)與聯(lián)調

接口暴露與引用
image.png

暴露接口的通常做法是 接口與實現分離,服務端將 接口、模型、異常 等統(tǒng)一放置于一個模塊,實現置于另一個模塊。調用方通過Maven進行引用。

注意:在分布式項目中,不會把整個服務提供方打包成JAR并提供給消費端,而是選擇單獨把接口與實體類打包成JAR提供給消費端。

自動化構建與協(xié)作

當項目越來越多,服務依懶關系越發(fā)復雜的時候,為了提高協(xié)作效率,必須采用自動化工具 完成 接口從編寫到構建成JAR包,最后到引用的整個過程。


image.png

流程描述:

  1. 服務提供者項目發(fā)人員編寫Client 接口
  2. push 至遠程倉庫
  3. jenkins 構建指定版本
  4. jenkins Deploye 至私服倉庫 nexus
  5. 服務消費者項目開發(fā)人員基于maven 從私服務倉庫下載
接口平滑升級

在項目迭代過程當中, 經常會有多個項目依懶同一個接口,如下圖 項目B、C都依懶了項目A當中的接口1,此時項目B業(yè)務需要,需要接口1多增加一個參數,升級完成后。項目B能正確構建上線,項目C卻不行。


image.png

解決辦法與原則:

  1. 接口要做到向下兼容:接口參數盡量以對象形式進行封裝。Model屬性只增不刪,如果需要作廢,可以添加@Deprecated 標識。
  2. 如果出現了不可兼容的變更,則必須通知調用方整改,并制定上線計劃。
-- 不推薦,兼容性差
public interface UserService {
    User getUser(Integer id, string name);
}
-- 推薦
public interface UserService {
    User getUser(UserParam param);
    public static class UserParam
    {
        Integer id;
        string name;
    }
}

二、Dubbo控制管理后臺使用

Dubbo 控制后臺的安裝
#從github 中下載dubbo 項目
git clone https://github.com/apache/incubator-dubbo.git
#更新項目
git fetch
#臨時切換至 dubbo-2.5.8 版本
git checkout dubbo-2.5.8
#進入 dubbo-admin 目錄
cd dubbo-admin
#mvn 構建admin war 包
mvn clean pakcage -DskipTests
#得到 dubbo-admin-2.5.8.war 即可直接部署至Tomcat
#修改 dubbo.properties 配置文件
dubbo.registry.address=zookeeper://127.0.0.1:2181

三、Dubbo注冊中心詳解

為了到達服務集群動態(tài)擴容的目的,注冊中心存儲了服務的地址信息與可用狀態(tài)信息,并實時推送給訂閱了相關服務的客戶端。


image.png

一個完整的注冊中心需要實現以下功能:

  1. 接收服務端的注冊與客戶端的引用,即將引用與消費建立關聯(lián),并支持多對多。
  2. 當服務非正常關閉時能即時清除其狀態(tài)
  3. 當注冊中心重啟時,能自動恢復注冊數據,以及訂閱請求
  4. 注冊中心本身的集群
Zookeeper 注冊中心

關于Zookeeper 注冊中心同樣需要了解其存儲結構和更新機制。
Zookeper是一個樹型的目錄服務,本身支持變更推送相比redis的實現Publish/Subscribe功能更穩(wěn)定。

image.png

注意:其中葉子節(jié)點為臨時節(jié)點。

源碼解析
image.png

注意:UserService是一個代理對象,由ReferenceConfig引用對象生成,并把ClusterInvoker、RegistryDirectory賦予給它。

四、Dubbo調用模塊

dubbo調用模塊核心功能是發(fā)起一個遠程方法的調用并順利拿到返回結果,其體系組成如下:

  1. 透明代理:通過動態(tài)代理技術,屏蔽遠程調用細節(jié)以提高編程友好性。這里dubbo 使用了 javassist作為代理實現。
  2. 負載均衡:當有多個提供者是,如何選擇哪個進行調用的負載算法。
  3. 容錯機制:當服務調用失敗時采取的策略
  4. 調用方式:支持同步調用、異步調用
負載均衡

Dubbo 目前官方支持以下負載均衡策略:

  1. 隨機(random):按權重設置隨機概率。此為默認算法.
  2. 輪循 (roundrobin):按公約后的權重設置輪循比率。
  3. 最少活躍調用數(leastactive):相同活躍數的隨機,活躍數指調用前后計數差。
  4. 一致性Hash(consistenthash ):相同的參數總是發(fā)到同一臺機器,默認初始化160個虛擬點,相對hash取余的方式,一致性Hash避免了單一服務過熱以及節(jié)點數量變化后全局亂套的缺點。


    image.png
容錯

Dubbo 官方目前支持以下容錯策略:

  1. 失敗自動切換:調用失敗后基于retries=“2” 屬性重試其它服務器
  2. 快速失?。嚎焖偈?,只發(fā)起一次調用,失敗立即報錯。
  3. 勿略失敗:失敗后勿略,不拋出異常給客戶端。
  4. 失敗重試:失敗自動恢復,后臺記錄失敗請求,定時重發(fā)。通常用于消息通知操作
  5. 并行調用: 只要一個成功即返回,并行調用指定數量機器,可通過 forks="2" 來設置最大并行數。
  6. 廣播調用:廣播調用所有提供者,逐個調用,任意一臺報錯則報錯
異步調用

異步調用是指發(fā)起遠程調用之后獲取結果的方式。

  1. 同步等待結果返回(默認)
  2. 異步等待結果返回
  3. 不需要返回結果
    Dubbo 中關于異步等待結果返回的實現流程如下圖:


    image.png
demoService.sayHello1("han");
Future<Object> future1 = RpcContext.getContext().getFuture();  //底層ThreadLocal
demoService.sayHello2("han2");
Future<Object> future2 = RpcContext.getContext().getFuture();
Object r1 = null, r2 = null;
// wait 直到拿到結果 獲超時
r1 = future1.get();  //同步
// wait 直到拿到結果 獲超時
r2 = future2.get();  //同步

注意:有返回值的時候,future.get()方法執(zhí)行的是同步,提升效率的關鍵在于多個future.get()方法同時執(zhí)行時,全部完成的時間是future.get()方法最慢的那一個,而不是多個future.get()方法的總和。
注意:同步調用底層也使用了Future,與異步不同的是,同步對異步多了一層包裝,里面使用了future.get()方法,一個線程死循環(huán)在訪問結果是否有值,一個線程在檢測線程是否超時。

五、Dubbo 調用非典型使用場景

泛化提供

是指不通過接口的方式直接將服務暴露出去。通常用于Mock框架或服務降級框架實現。
模擬出來的通用服務提供方

public class MockService implements GenericService {
    private String target;

    public MockService(String target) {
        this.target = target;
    }

    // 通用方法
    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        if (target.equals("com.lin.client.UserService") && method.equals("getUser")) {
            HashMap<Object, Object> map = new HashMap<>();
            map.put("id", 1);
            map.put("name", "克里斯");
            return map;
        } 
        return null;
    }
}
public class DubboServer {
    public static void main(String[] args) throws IOException {
        ApplicationConfig applicationConfig = new ApplicationConfig("sample-app");
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setSerialization("fastjson");
        protocolConfig.setPort(-1);//20880
        RegistryConfig registryConfig = new RegistryConfig("zookeeper://192.168.0.147:2181");

        ServiceConfig serviceConfig = new ServiceConfig();
        serviceConfig.setInterface("com.tuling.client.UserService");
        //serviceConfig.setRef(new UserServiceImpl());
        setMock("com.lin.client.UserService");
        serviceConfig.setRegistry(registryConfig);
        serviceConfig.setProtocol(protocolConfig);
        serviceConfig.setApplication(applicationConfig);
        serviceConfig.export();
        System.out.println("服務已暴露");
        System.in.read();
    }

    public static void setMock(ServiceConfig serviceConfig, String server) {
        serviceConfig.setRef(new MockService(server));
    }
}
隱示傳參

是指通過非常方法參數傳遞參數,類似于http 調用當中添加cookie值。通常用于分布式追蹤框架的實現。使用方式如下 :

//客戶端隱示設置值
RpcContext.getContext().setAttachment("index", "1"); // 隱式傳參,后面的遠程調用都會隱
//服務端隱示獲取值
String index = RpcContext.getContext().getAttachment("index"); 
令牌驗證

通過令牌驗證在注冊中心控制權限,以決定要不要下發(fā)令牌給消費者,可以防止消費者繞過注冊中心訪問提供者,另外通過注冊中心可靈活改變授權方式,而不需修改或升級提供者


image.png

使用:

<!--隨機token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
過濾器

類似于 WEB 中的Filter ,Dubbo本身提供了Filter 功能用于攔截遠程方法的調用。其支持自定義過濾器與官方的過濾器使用:

<dubbo:provider  filter="accesslog" accesslog="logs/dubbo.log"/>

以上配置 就是 為 服務提供者 添加 日志記錄過濾器, 所有訪問日志將會集中打印至 accesslog 當中。
自定義過濾器:

  • 編寫過濾器
package com.tuling.dubbo;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
@Activate(group = {CommonConstants.PROVIDER})
public class ProviderHelloFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("log========>");
        return invoker.invoke(invocation);
    }
}
  • 添加擴展點
# 文件路徑
META-INF/dubbo/org.apache.dubbo.rpc.Filter
#內容:
helloFilter=com.tuling.dubbo.ProviderHelloFilter

六、調用內部實現源碼分析

分析代理類

在調用服務端時,是接口的形式進行調用,該接口是Duboo 動態(tài)代理之后的實現,通過反編譯工具可以查看到其具體實現:
因為類是代理生成,所以采用arthas工具來反編譯,具體操作如下:

#運行 arthas
java -jar arthas-boot.jar
#掃描類
sc *.proxy0
#反編譯代理類
jad com.alibaba.dubbo.common.bytecode.proxy0

反編譯的代碼如下:

package org.apache.dubbo.common.bytecode;

import com.alibaba.dubbo.rpc.service.EchoService;
import com.tuling.client.User;
import com.tuling.client.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.dubbo.common.bytecode.ClassGenerator;

public class proxy0 implements ClassGenerator.DC, EchoService, UserService {
    public static Method[] methods;
    private InvocationHandler handler;
    public List findUser(String string, String string2) {
        Object[] arrobject = new Object[]{string, string2};
        Object object = this.handler.invoke(this, methods[0], arrobject);
        return (List)object;
    }
    public User getUser(Integer n) {
        Object[] arrobject = new Object[]{n};
        Object object = this.handler.invoke(this, methods[1], arrobject);
        return (User)object;
    }
    @Override
    public Object $echo(Object object) {
        Object[] arrobject = new Object[]{object};
        Object object2 = this.handler.invoke(this, methods[2], arrobject);
        return object2;
    }
    public proxy0() {
    }
    public proxy0(InvocationHandler invocationHandler) {
        this.handler = invocationHandler;
    }
}

可看出其代理實現了 UserService 接口。并且基于InvocationHandler 進行代理。實際類是 InvokerInvocationHandler 并且其中之屬性為Invoker.。也就是說最終會調用Invoker進行遠程調用。

Dubbo調用流程
image.png
//------7協(xié)議 調用
doInvoke:77, DubboInvoker {org.apache.dubbo.rpc.protocol.dubbo}
invoke:155, AbstractInvoker {org.apache.dubbo.rpc.protocol}
//------6異步轉同步
invoke:52, AsyncToSyncInvoker {org.apache.dubbo.rpc.protocol} // 異步轉同步 ,返回結果之前進行阻塞調用線程
//----- 5過濾器鏈
invoke:92, MonitorFilter {org.apache.dubbo.monitor.support}  // 過濾鏈-> 監(jiān)控器
invoke:54, FutureFilter {org.apache.dubbo.rpc.protocol.dubbo.filter}    //過濾鏈-> 回調參數
invoke:14, ProviderHelloFilter {com.tuling.dubbo}  // 過濾鏈-> 自定義過濾器
invoke:60, ConsumerContextFilter {org.apache.dubbo.rpc.filter} // 過濾鏈-> 消費者環(huán)境初始化
//------4集群處理
doInvoke:82, FailoverClusterInvoker {org.apache.dubbo.rpc.cluster.support} // 集服-失敗重試
invoke:248, AbstractClusterInvoker {org.apache.dubbo.rpc.cluster.support} //
//----- 3Mock服務
invoke:78, MockClusterInvoker {org.apache.dubbo.rpc.cluster.support.wrapper} // mock 服務
//----- 2動態(tài)代理 --透明化
invoke:55, InvokerInvocationHandler {org.apache.dubbo.rpc.proxy}// 代理的中間接口
getUser:-1, proxy0 {org.apache.dubbo.common.bytecode} // 代理對象
//----- 1調用客戶端
main:53, DubboClient {com.tuling.dubbo}  // 客戶端

協(xié)議-->注冊協(xié)議--->MockClusterInvoker--->ClusterInvoker--->RegistryDirectory--->DubboProtcol->FilterChain-->DubboInvoker
image.png

注意:核心在于運用了責任鏈模式與spi擴展點的技術。

七、RPC 協(xié)議

在一個典型RPC的使用場景中,包含了服務發(fā)現、負載、容錯、網絡傳輸、序列化等組件,其中RPC協(xié)議就指明了程序如何進行網絡傳輸和序列化 。也就是說一個RPC協(xié)議的實現就等于一個非透明的遠程調用實現,如何做到的的呢?


image.png
dubbo 支持的RPC協(xié)議列表
image.png
dubbo協(xié)議結構
image.png
  • magic:類似java字節(jié)碼文件里的魔數,用來判斷是不是dubbo協(xié)議的數據包。魔數是常量0xdabb,用于判斷報文的開始。
  • flag:標志位, 一共8個地址位。低四位用來表示消息體數據用的序列化工具的類型(默認hessian),高四位中,第一位為1表示是request請求,第二位為1表示雙向傳輸(即有返回response),第三位為1表示是心跳ping事件。
  • status:狀態(tài)位, 設置請求響應狀態(tài),dubbo定義了一些響應的類型。具體類型見 com.alibaba.dubbo.remoting.exchange.Response
  • invoke id:消息id, long 類型。每一個請求的唯一識別id(由于采用異步通訊的方式,用來把請求request和返回的response對應上)
  • body length:消息體 body 長度, int 類型,即記錄Body Content有多少個字節(jié)。


    image.png
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容