自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于Dubbo隨便問八個問題

開發(fā) 前端
互聯(lián)網(wǎng)公司的系統(tǒng)有成千上萬個大大小小的服務(wù)組成,服務(wù)各自部署在不同的機(jī)器上,服務(wù)間的調(diào)用需要用到網(wǎng)絡(luò)通信,服務(wù)消費方每調(diào)用一個服務(wù)都要寫一坨網(wǎng)絡(luò)通信相關(guān)的代碼,不僅復(fù)雜而且極易出錯。

[[374925]]

本文轉(zhuǎn)載自微信公眾號「sowhat1412 」,作者sowhat1412 。轉(zhuǎn)載本文請聯(lián)系sowhat1412 公眾號。   

1、RPC

1.1 RPC 定義

互聯(lián)網(wǎng)公司的系統(tǒng)有成千上萬個大大小小的服務(wù)組成,服務(wù)各自部署在不同的機(jī)器上,服務(wù)間的調(diào)用需要用到網(wǎng)絡(luò)通信,服務(wù)消費方每調(diào)用一個服務(wù)都要寫一坨網(wǎng)絡(luò)通信相關(guān)的代碼,不僅復(fù)雜而且極易出錯。還要考慮新服務(wù)依賴?yán)戏?wù)時如何調(diào)用老服務(wù),別的服務(wù)依賴新服務(wù)的時候新服務(wù)如何發(fā)布方便他人調(diào)用。如何解決這個問題呢?業(yè)界一般采用RPC遠(yuǎn)程調(diào)用的方式來實現(xiàn)。

RPC:

Remote Procedure Call Protocol 既 遠(yuǎn)程過程調(diào)用,一種能讓我們像調(diào)用本地服務(wù)一樣調(diào)用遠(yuǎn)程服務(wù),可以讓調(diào)用者對網(wǎng)絡(luò)通信這些細(xì)節(jié)無感知,比如服務(wù)消費方在執(zhí)行 helloWorldService.sayHello("sowhat") 時,實質(zhì)上調(diào)用的是遠(yuǎn)端的服務(wù)。這種方式其實就是RPC,RPC思想在各大互聯(lián)網(wǎng)公司中被廣泛使用,如阿里巴巴的dubbo、當(dāng)當(dāng)?shù)腄ubbox 、Facebook 的 thrift、Google 的grpc、Twitter的finagle等。

1.2 RPC demo

說了那么多,還是實現(xiàn)一個簡易版的RPC demo吧。

1.2.1 公共接口

  1. public interface SoWhatService { 
  2.     String sayHello(String name);   
  3. }  

1.2.2 服務(wù)提供者

接口類實現(xiàn)

  1. public class SoWhatServiceImpl implements SoWhatService 
  2.  @Override 
  3.  public String sayHello(String name
  4.  { 
  5.   return "你好啊 " + name
  6.  } 
  7. }   

服務(wù)注冊對外提供者

  1. /** 
  2.  * 服務(wù)注冊對外提供者 
  3.  */ 
  4.  
  5. public class ServiceFramework 
  6.  public static void export(Object service, int port) throws Exception 
  7.  { 
  8.   ServerSocket server = new ServerSocket(port); 
  9.   while (true
  10.   { 
  11.    Socket socket = server.accept(); 
  12.    new Thread(() -> 
  13.    { 
  14.     try 
  15.     { 
  16.      //反序列化 
  17.      ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); 
  18.      //讀取方法名 
  19.      String methodName =(String) input.readObject(); 
  20.      //參數(shù)類型 
  21.      Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); 
  22.      //參數(shù) 
  23.      Object[] arguments = (Object[]) input.readObject(); 
  24.      //找到方法 
  25.      Method method = service.getClass().getMethod(methodName, parameterTypes); 
  26.      //調(diào)用方法 
  27.      Object result = method.invoke(service, arguments); 
  28.      // 返回結(jié)果 
  29.      ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); 
  30.      output.writeObject(result); 
  31.     } catch (Exception e) 
  32.     { 
  33.      e.printStackTrace(); 
  34.     } 
  35.    }).start(); 
  36.   } 
  37.  } 

服務(wù)運行

  1. public class ServerMain 
  2.  public static void main(String[] args) 
  3.  { 
  4.   //服務(wù)提供者 暴露出接口 
  5.   SoWhatService service = new SoWhatServiceImpl(); 
  6.   try 
  7.   { 
  8.    ServiceFramework.export(service, 1412); 
  9.   } catch (Exception e) 
  10.   { 
  11.    e.printStackTrace(); 
  12.   } 
  13.  } 

1.2.3 服務(wù)調(diào)用者

動態(tài)代理調(diào)用遠(yuǎn)程服務(wù)

  1. /** 
  2.  * @author sowhat 
  3.  * 動態(tài)代理調(diào)用遠(yuǎn)程服務(wù) 
  4.  */ 
  5. public class RpcFunction 
  6.  public static <T> T refer(Class<T> interfaceClass, String host, int port) throws Exception 
  7.  { 
  8.   return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, 
  9.     new InvocationHandler() 
  10.     { 
  11.      @Override 
  12.      public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable 
  13.      { 
  14.       //指定 provider 的 ip 和端口 
  15.       Socket socket = new Socket(host, port); 
  16.       ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); 
  17.       //傳方法名 
  18.       output.writeObject(method.getName()); 
  19.       //傳參數(shù)類型 
  20.       output.writeObject(method.getParameterTypes()); 
  21.       //傳參數(shù)值 
  22.       output.writeObject(arguments); 
  23.       ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); 
  24.       //讀取結(jié)果 
  25.       Object result = input.readObject(); 
  26.       return result; 
  27.      } 
  28.     }); 
  29.  } 

調(diào)用方法

  1. public class RunMain 
  2.  public static void main(String[] args) 
  3.  { 
  4.   try 
  5.   { 
  6.    //服務(wù)調(diào)用者 需要設(shè)置依賴 
  7.    SoWhatService service = RpcFunction.refer(SoWhatService.class, "127.0.0.1", 1412); 
  8.    System.out.println(service.sayHello(" sowhat1412")); 
  9.   } catch (Exception e) 
  10.   { 
  11.    e.printStackTrace(); 
  12.   } 
  13.  } 

2、Dubbo 框架設(shè)計

2.1 Dubbo 簡介

Dubbo 是阿里巴巴研發(fā)開源工具,主要分為2.6.x 跟 2.7.x 版本。是一款分布式、高性能、透明化的 RPC 服務(wù)框架,提供服務(wù)自動注冊、自動發(fā)現(xiàn)等高效服務(wù)治理方案,可以和Spring 框架無縫集成,它提供了6大核心能力:

1. 面向接口代理的高性能RPC調(diào)用

2. 智能容錯和負(fù)載均衡

3. 服務(wù)自動注冊和發(fā)現(xiàn)

4. 高度可擴(kuò)展能力

5. 運行期流量調(diào)度

6. 可視化的服務(wù)治理與運維

調(diào)用過程:

  1. 服務(wù)提供者 Provider 啟動然后向 Registry 注冊自己所能提供的服務(wù)。
  2. 服務(wù)消費者 Consumer 向Registry訂閱所需服務(wù),Consumer 解析Registry提供的元信息,從服務(wù)中通過負(fù)載均衡選擇 Provider調(diào)用。
  3. 服務(wù)提供方 Provider 元數(shù)據(jù)變更的話Registry會把變更推送給Consumer,以此保證Consumer獲得最新可用信息。

注意點:

Provider 跟 Consumer 在內(nèi)存中記錄調(diào)用次數(shù)跟時間,定時發(fā)送統(tǒng)計數(shù)據(jù)到Monitor,發(fā)送的時候是短連接。

Monitor 跟 Registry 是可選的,可直接在配置文件中寫好,Provider 跟 Consumer進(jìn)行直連。

Monitor 跟 Registry 掛了也沒事, Consumer 本地緩存了 Provider 信息。

Consumer 直接調(diào)用 Provider 不會經(jīng)過 Registry。Provider、Consumer這倆到 Registry之間是長連接。

2.2 Dubbo框架分層

如上圖,總的而言 Dubbo 分為三層。

  1. Busines層:由用戶自己來提供接口和實現(xiàn)還有一些配置信息。
  2. RPC層:真正的RPC調(diào)用的核心層,封裝整個RPC的調(diào)用過程、負(fù)載均衡、集群容錯、代理。
  3. Remoting層:對網(wǎng)絡(luò)傳輸協(xié)議和數(shù)據(jù)轉(zhuǎn)換的封裝。

如果每一層再細(xì)分下去,一共有十層。

  1. 接口服務(wù)層(Service):該層與業(yè)務(wù)邏輯相關(guān),根據(jù) provider 和 consumer 的業(yè)務(wù)設(shè)計對應(yīng)的接口和實現(xiàn)。
  2. 配置層(Config):對外配置接口,以 ServiceConfig 和 ReferenceConfig 為中心初始化配置。
  3. 服務(wù)代理層(Proxy):服務(wù)接口透明代理,Provider跟Consumer都生成代理類,使得服務(wù)接口透明,代理層實現(xiàn)服務(wù)調(diào)用跟結(jié)果返回。
  4. 服務(wù)注冊層(Registry):封裝服務(wù)地址的注冊和發(fā)現(xiàn),以服務(wù) URL 為中心。
  5. 路由層(Cluster):封裝多個提供者的路由和負(fù)載均衡,并橋接注冊中心,以Invoker 為中心,擴(kuò)展接口為 Cluster、Directory、Router 和 LoadBlancce。
  6. 監(jiān)控層(Monitor):RPC 調(diào)用次數(shù)和調(diào)用時間監(jiān)控,以 Statistics 為中心,擴(kuò)展接口為 MonitorFactory、Monitor 和 MonitorService。
  7. 遠(yuǎn)程調(diào)用層(Protocal):封裝 RPC 調(diào)用,以 Invocation 和 Result 為中心,擴(kuò)展接口為 Protocal、Invoker 和 Exporter。
  8. 信息交換層(Exchange):封裝請求響應(yīng)模式,同步轉(zhuǎn)異步。以 Request 和Response 為中心,擴(kuò)展接口為 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer。
  9. 網(wǎng)絡(luò)傳輸層(Transport):抽象 mina 和 netty 為統(tǒng)一接口,以 Message 為中心,擴(kuò)展接口為 Channel、Transporter、Client、Server 和 Codec。
  10. 數(shù)據(jù)序列化層(Serialize):可復(fù)用的一些工具,擴(kuò)展接口為 Serialization、ObjectInput、ObjectOutput 和 ThreadPool。

他們之間的調(diào)用關(guān)系直接看下面官網(wǎng)圖即可。

3、Dubbo SPI 機(jī)制

Dubbo 采用 微內(nèi)核設(shè)計 + SPI 擴(kuò)展技術(shù)來搭好核心框架,同時滿足用戶定制化需求。這里重點說下SPI。

3.1 微內(nèi)核

操作系統(tǒng)層面的微內(nèi)核跟宏內(nèi)核:

微內(nèi)核Microkernel:是一種內(nèi)核的設(shè)計架構(gòu),由盡可能精簡的程序所組成,以實現(xiàn)一個操作系統(tǒng)所需要的最基本功能,包括了底層的尋址空間管理、線程管理、與進(jìn)程間通信。成功案例是QNX系統(tǒng),比如黑莓手機(jī)跟車用市場。

宏內(nèi)核Monolithic :把 進(jìn)程管理、內(nèi)存管理、文件系統(tǒng)、進(jìn)程通信等功能全部作為內(nèi)核來實現(xiàn),而微內(nèi)核則僅保留最基礎(chǔ)的功能,Linux 就是宏內(nèi)核架構(gòu)設(shè)計。

Dubbo中的廣義微內(nèi)核:

思想是 核心系統(tǒng) + 插件,說白了就是把不變的功能抽象出來稱為核心,把變動的功能作為插件來擴(kuò)展,符合開閉原則,更容易擴(kuò)展、維護(hù)。比如小霸王游戲機(jī)中機(jī)體本身作為核心系統(tǒng),游戲片就是插件。vscode、Idea、chrome等都是微內(nèi)核的產(chǎn)物。

微內(nèi)核架構(gòu)其實是一直架構(gòu)思想,可以是框架層面也可以是某個模塊設(shè)計,它的本質(zhì)就是將變化的部分抽象成插件,使得可以快速簡便地滿足各種需求又不影響整體的穩(wěn)定性。

3.2 SPI 含義

主流的數(shù)據(jù)庫有MySQL、Oracle、DB2等,這些數(shù)據(jù)庫是不同公司開發(fā)的,它們的底層協(xié)議不大一樣,那怎么約束呢?一般就是定制統(tǒng)一接口,具體實現(xiàn)不管,反正面向相同的接口編程即可。等到真正使用的時候用具體的實現(xiàn)類就好,問題是哪里找用那個實現(xiàn)類呢?這時候就采用約定好的法則將實現(xiàn)類寫到指定位置即可。

SPI 全稱為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。它約定在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。

3.3 SPI demo

接口:

  1. package com.example.demo.spi; 
  2.  
  3. public interface SPIService { 
  4.     void execute(); 

實現(xiàn)類1:

  1. public class SpiImpl1 implements SPIService{ 
  2.  @Override 
  3.     public void execute() { 
  4.         System.out.println("SpiImpl1.execute()"); 
  5.     } 

實現(xiàn)類2:

  1. public class SpiImpl2 implements SPIService{ 
  2.  @Override 
  3.     public void execute() { 
  4.   System.out.println("SpiImpl2.execute()"); 
  5.     } 

配置路徑

調(diào)用加載類

  1. package com.example.demo.spi; 
  2. import sun.misc.Service; 
  3. import java.util.Iterator; 
  4. import java.util.ServiceLoader; 
  5.  
  6. public class Test { 
  7.     public static void main(String[] args) {     
  8.         Iterator<SPIService> providers = Service.providers(SPIService.class); 
  9.         ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); 
  10.  
  11.         while(providers.hasNext()) { 
  12.             SPIService ser = providers.next(); 
  13.             ser.execute(); 
  14.         } 
  15.         System.out.println("--------------------------------"); 
  16.         Iterator<SPIService> iterator = load.iterator(); 
  17.         while(iterator.hasNext()) { 
  18.             SPIService ser = iterator.next(); 
  19.             ser.execute(); 
  20.         } 
  21.     } 

3.4 SPI源碼追蹤

ServiceLoader.load(SPIService.class) 底層調(diào)用大致邏輯如下:圖片iterator.hasNext() 跟 iterator.next()底層調(diào)用大致如下:

3.5 Java SPI缺點

不能按需加載,Java SPI在加載擴(kuò)展點的時候,會一次性加載所有可用的擴(kuò)展點,很多是不需要的,會浪費系統(tǒng)資源。

獲取某個實現(xiàn)類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據(jù)某個參數(shù)來獲取對應(yīng)的實現(xiàn)類。

不支持AOP與依賴注入,JAVA SPI可能會丟失加載擴(kuò)展點異常信息,導(dǎo)致追蹤問題很困難。

3.6 Dubbo SPI

JDK自帶的不好用Dubbo 就自己實現(xiàn)了一個 SPI,該SPI 可以通過名字實例化指定的實現(xiàn)類,并且實現(xiàn)了 IOC 、AOP 與 自適應(yīng)擴(kuò)展 SPI 。

  1. key = com.sowhat.value 

Dubbo 對配置文件目錄的約定,不同于 Java SPI ,Dubbo 分為了三類目錄。

META-INF/services/ :該目錄下 SPI 配置文件是為了用來兼容 Java SPI 。

META-INF/dubbo/ :該目錄存放用戶自定義的 SPI 配置文件。

META-INF/dubbo/internal/ :該目錄存 Dubbo 內(nèi)部使用的 SPI 配置文件。

使用的話很簡單 引入依賴,然后百度教程即可。

  1. @Test 
  2.  void sowhat() 
  3.  { 
  4.   ExtensionLoader<SPIService> spiService = ExtensionLoader.getExtensionLoader(SPIService.class);        //按需獲取實現(xiàn)類對象 
  5.   SPIService demo1 = spiService.getExtension("SpiImpl1"); 
  6.   demo1.execute(); 
  7.  } 

3.7 Dubbo SPI源碼追蹤

ExtensionLoader.getExtension 方法的整個思路是 查找緩存是否存在,不存在則讀取SPI文件,通過反射創(chuàng)建類,然后設(shè)置依賴注入這些東西,有包裝類就包裝下,執(zhí)行流程如下圖所示:

說下重要的四個部分:

1.injectExtension IOC

查找 set 方法,根據(jù)參數(shù)找到依賴對象則注入。

2.WrapperClass AOP

包裝類,Dubbo 幫你自動包裝,只需要某個擴(kuò)展類的構(gòu)造函數(shù)只有一個參數(shù),并且是擴(kuò)展接口類型,就會被判定為包裝類。

3.Activate

Active 有三個屬性,group 表示修飾在哪個端,是 provider 還是 consumer,value 表示在 URL參數(shù)中出現(xiàn)才會被激活,order 表示實現(xiàn)類的順序。

3.8 Adaptive 自適應(yīng)擴(kuò)展

需求:根據(jù)配置來進(jìn)行 SPI 擴(kuò)展的加載后不想在啟動的時候讓擴(kuò)展被加載,想根據(jù)請求時候的參數(shù)來動態(tài)選擇對應(yīng)的擴(kuò)展。實現(xiàn):Dubbo用代理機(jī)制實現(xiàn)了自適應(yīng)擴(kuò)展,為用戶想擴(kuò)展的接口 通過JDK 或者 Javassist 編譯生成一個代理類,然后通過反射創(chuàng)建實例。實例會根據(jù)本來方法的請求參數(shù)得知需要的擴(kuò)展類,然后通過 ExtensionLoader.getExtensionLoader(type.class).getExtension(name)來獲取真正的實例來調(diào)用,看個官網(wǎng)樣例。

  1. public interface WheelMaker { 
  2.     Wheel makeWheel(URL url); 
  3. // WheelMaker 接口的自適應(yīng)實現(xiàn)類 
  4. public class AdaptiveWheelMaker implements WheelMaker { 
  5.     public Wheel makeWheel(URL url) { 
  6.         if (url == null) { 
  7.             throw new IllegalArgumentException("url == null"); 
  8.         } 
  9.      // 1. 調(diào)用 url 的 getXXX 方法獲取參數(shù)值 
  10.         String wheelMakerName = url.getParameter("Wheel.maker"); 
  11.         if (wheelMakerName == null) { 
  12.             throw new IllegalArgumentException("wheelMakerName == null"); 
  13.         } 
  14.         // 2. 調(diào)用 ExtensionLoader 的 getExtensionLoader 獲取加載器 
  15.         // 3. 調(diào)用 ExtensionLoader 的 getExtension 根據(jù)從url獲取的參數(shù)作為類名稱加載實現(xiàn)類 
  16.         WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName); 
  17.         // 4. 調(diào)用實現(xiàn)類的具體方法實現(xiàn)調(diào)用。 
  18.         return wheelMaker.makeWheel(URL url); 
  19.     } 

查看Adaptive注解源碼可知該注解可用在類或方法上,Adaptive 注解在類上或者方法上有不同的實現(xiàn)邏輯。

7.8.1 Adaptive 注解在類上

Adaptive 注解在類上時,Dubbo 不會為該類生成代理類,Adaptive 注解在類上的情況很少,在 Dubbo 中,僅有兩個類被 Adaptive 注解了,分別是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加載邏輯由人工編碼完成,這不是我們關(guān)注的重點。

7.8.2 Adaptive 注解在方法上

Adaptive 注解在方法上時,Dubbo 則會為該方法生成代理邏輯,表示拓展的加載邏輯需由框架自動生成,大致的實現(xiàn)機(jī)制如下:

加載標(biāo)注有 @Adaptive 注解的接口,如果不存在,則不支持 Adaptive 機(jī)制;

為目標(biāo)接口按照一定的模板生成子類代碼,并且編譯生成的代碼,然后通過反射生成該類的對象;

結(jié)合生成的對象實例,通過傳入的URL對象,獲取指定key的配置,然后加載該key對應(yīng)的類對象,最終將調(diào)用委托給該類對象進(jìn)行。

  1. @SPI("apple"
  2. public interface FruitGranter { 
  3.   Fruit grant(); 
  4.   @Adaptive 
  5.   String watering(URL url); 
  6. --- 
  7. // 蘋果種植者 
  8. public class AppleGranter implements FruitGranter { 
  9.   @Override 
  10.   public Fruit grant() { 
  11.     return new Apple(); 
  12.   } 
  13.   @Override 
  14.   public String watering(URL url) { 
  15.     System.out.println("watering apple"); 
  16.     return "watering finished"
  17.   } 
  18. --- 
  19. // 香蕉種植者 
  20. public class BananaGranter implements FruitGranter { 
  21.   @Override 
  22.   public Fruit grant() { 
  23.     return new Banana(); 
  24.   } 
  25.   @Override 
  26.   public String watering(URL url) { 
  27.     System.out.println("watering banana"); 
  28.     return "watering success"
  29.   } 

調(diào)用方法實現(xiàn):

  1. public class ExtensionLoaderTest { 
  2.   @Test 
  3.   public void testGetExtensionLoader() { 
  4.     // 首先創(chuàng)建一個模擬用的URL對象 
  5.     URL url = URL.valueOf("dubbo://192.168.0.1:1412?fruit.granter=apple"); 
  6.     // 通過ExtensionLoader獲取一個FruitGranter對象 
  7.     FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class) 
  8.       .getAdaptiveExtension(); 
  9.     // 使用該FruitGranter調(diào)用其"自適應(yīng)標(biāo)注的"方法,獲取調(diào)用結(jié)果 
  10.     String result = granter.watering(url); 
  11.     System.out.println(result); 
  12.   } 

通過如上方式生成一個內(nèi)部類。大致調(diào)用流程如下:

4、Dubbo 服務(wù)暴露流程

4.1 服務(wù)暴露總覽

Dubbo框架是以URL為總線的模式,運行過程中所有的狀態(tài)數(shù)據(jù)信息都可以通過URL來獲取,比如當(dāng)前系統(tǒng)采用什么序列化,采用什么通信,采用什么負(fù)載均衡等信息,都是通過URL的參數(shù)來呈現(xiàn)的,所以在框架運行過程中,運行到某個階段需要相應(yīng)的數(shù)據(jù),都可以通過對應(yīng)的Key從URL的參數(shù)列表中獲取。URL 具體的參數(shù)如下:

protocol:指的是 dubbo 中的各種協(xié)議,如:dubbo thrift http username/password:用戶名/密碼 host/port:主機(jī)/端口 path:接口的名稱 parameters:參數(shù)鍵值對

  1. protocol://username:password@host:port/path?k=v 

服務(wù)暴露從代碼流程看分為三部分:

  1. 檢查配置,最終組裝成 URL。
  2. 暴露服務(wù)到到本地服務(wù)跟遠(yuǎn)程服務(wù)。
  3. 服務(wù)注冊至注冊中心。

服務(wù)暴露從對象構(gòu)建轉(zhuǎn)換看分為兩步:

  1. 將服務(wù)封裝成Invoker。
  2. 將Invoker通過協(xié)議轉(zhuǎn)換為Exporter。

4.2 服務(wù)暴露源碼追蹤

  1. 容器啟動,Spring IOC 刷新完畢后調(diào)用 onApplicationEvent 開啟服務(wù)暴露,ServiceBean 。
  2. export 跟 doExport 來進(jìn)行拼接構(gòu)建URL,為屏蔽調(diào)用的細(xì)節(jié),統(tǒng)一暴露出一個可執(zhí)行體,通過ProxyFactory 獲取到 invoker。
  3. 調(diào)用具體 Protocol 將把包裝后的 invoker 轉(zhuǎn)換成 exporter,此處用到了SPI。
  4. 然后啟動服務(wù)器server,監(jiān)聽端口,使用NettyServer創(chuàng)建監(jiān)聽服務(wù)器。
  5. 通過 RegistryProtocol 將URL注冊到注冊中心,使得consumer可獲得provider信息。圖片

5、Dubbo 服務(wù)引用流程

Dubbo中一個可執(zhí)行體就是一個invoker,所以 provider 跟 consumer 都要向 invoker 靠攏。通過上面demo可知為了無感調(diào)用遠(yuǎn)程接口,底層需要有個代理類包裝 invoker。

服務(wù)的引入時機(jī)有兩種:

餓漢式:

通過實現(xiàn) Spring 的 InitializingBean 接口中的 afterPropertiesSet 方法,容器通過調(diào)用 ReferenceBean的 afterPropertiesSet 方法時引入服務(wù)。

懶漢式(默認(rèn)):

懶漢式是只有當(dāng)服務(wù)被注入到其他類中時啟動引入流程。

服務(wù)引用的三種方式:

  1. 本地引入:服務(wù)暴露時本地暴露,避免網(wǎng)絡(luò)調(diào)用開銷。
  2. 直接連接引入遠(yuǎn)程服務(wù):不啟動注冊中心,直接寫死遠(yuǎn)程Provider地址 進(jìn)行直連。
  3. 通過注冊中心引入遠(yuǎn)程服務(wù):通過注冊中心抉擇如何進(jìn)行負(fù)載均衡調(diào)用遠(yuǎn)程服務(wù)。

服務(wù)引用流程:

  1. 檢查配置構(gòu)建map ,map 構(gòu)建 URL ,通過URL上的協(xié)議利用自適應(yīng)擴(kuò)展機(jī)制調(diào)用對應(yīng)的 protocol.refer 得到相應(yīng)的 invoker ,此處
  2. 想注冊中心注冊自己,然后訂閱注冊中心相關(guān)信息,得到provider的 ip 等信息,再通過共享的netty客戶端進(jìn)行連接。
  3. 當(dāng)有多個 URL 時,先遍歷構(gòu)建出 invoker 然后再由 StaticDirectory 封裝一下,然后通過 cluster 進(jìn)行合并,只暴露出一個 invoker 。
  4. 然后再構(gòu)建代理,封裝 invoker 返回服務(wù)引用,之后 Comsumer 調(diào)用的就是這個代理類。

 

調(diào)用方式:

oneway:不關(guān)心請求是否發(fā)送成功。

Async異步調(diào)用:Dubbo天然異步,客戶端調(diào)用請求后將返回的 ResponseFuture 存到上下文中,用戶可隨時調(diào)用 future.get 獲取結(jié)果。異步調(diào)用通過唯一ID 標(biāo)識此次請求。

Sync同步調(diào)用:在 Dubbo 源碼中就調(diào)用了 future.get,用戶感覺方法被阻塞了,必須等結(jié)果后才返回。

6、Dubbo 調(diào)用整體流程

調(diào)用之前你可能需要考慮這些事:

  1. consumer 跟 provider 約定好通訊協(xié)議,dubbo支持多種協(xié)議,比如dubbo、rmi、hessian、http、webservice等。默認(rèn)走dubbo協(xié)議,連接屬于單一長連接,NIO異步通信。適用傳輸數(shù)據(jù)量很小(單次請求在100kb以內(nèi)),但是并發(fā)量很高。
  2. 約定序列化模式,大致分為兩大類,一種是字符型(XML或json 人可看懂 但傳輸效率低),一種是二進(jìn)制流(數(shù)據(jù)緊湊,機(jī)器友好)。默認(rèn)使用 hessian2作為序列化協(xié)議。
  3. consumer 調(diào)用 provider 時提供對應(yīng)接口、方法名、參數(shù)類型、參數(shù)值、版本號。
  4. provider列表對外提供服務(wù)涉及到負(fù)載均衡選擇一個provider提供服務(wù)。
  5. consumer 跟 provider 定時向monitor 發(fā)送信息。

調(diào)用大致流程:

  1. 客戶端發(fā)起請求來調(diào)用接口,接口調(diào)用生成的代理類。代理類生成RpcInvocation 然后調(diào)用invoke方法。
  2. ClusterInvoker獲得注冊中心中服務(wù)列表,通過負(fù)載均衡給出一個可用的invoker。
  3. 序列化跟反序列化網(wǎng)絡(luò)傳輸數(shù)據(jù)。通過NettyServer調(diào)用網(wǎng)絡(luò)服務(wù)。
  4. 服務(wù)端業(yè)務(wù)線程池接受解析數(shù)據(jù),從exportMap找到invoker進(jìn)行invoke。
  5. 調(diào)用真正的Impl得到結(jié)果然后返回。

調(diào)用方式:

oneway:不關(guān)心請求是否發(fā)送成功,消耗最小。

sync同步調(diào)用:在 Dubbo 源碼中就調(diào)用了 future.get,用戶感覺方法被阻塞了,必須等結(jié)果后才返回。

Async 異步調(diào)用:Dubbo天然異步,客戶端調(diào)用請求后將返回的 ResponseFuture 存到上下文中,用戶可以隨時調(diào)用future.get獲取結(jié)果。異步調(diào)用通過唯一ID標(biāo)識此次請求。

7、Dubbo集群容錯負(fù)載均衡

Dubbo 引入了Cluster、Directory、Router、LoadBalance、Invoker模塊來保證Dubbo系統(tǒng)的穩(wěn)健性,它們的關(guān)系如下圖:

服務(wù)發(fā)現(xiàn)時會將多個多個遠(yuǎn)程調(diào)用放入Directory,然后通過Cluster封裝成一個Invoker,該invoker提供容錯功能。

消費者代用的時候從Directory中通過負(fù)載均衡獲得一個可用invoker,最后發(fā)起調(diào)用。

你可以認(rèn)為Dubbo中的Cluster對上面進(jìn)行了大的封裝,自帶各種魯棒性功能。

7.1 集群容錯

集群容錯是在消費者端通過Cluster子類實現(xiàn)的,Cluster接口有10個實現(xiàn)類,每個Cluster實現(xiàn)類都會創(chuàng)建一個對應(yīng)的ClusterInvoker對象。核心思想是讓用戶選擇性調(diào)用這個Cluster中間層,屏蔽后面具體實現(xiàn)細(xì)節(jié)。

Cluster Cluster Invoker 作用
FailoverCluster FailoverClusterInvoker 失敗自動切換功能,默認(rèn)
FailfastCluster FailfastClusterInvoker 一次調(diào)用,失敗異常
FailsafeCluster FailsafeClusterInvoker 調(diào)用出錯則日志記錄
FailbackCluster FailbackClusterInvoker 失敗返空,定時重試2次
ForkingCluster ForkingClusterInvoker 一個任務(wù)并發(fā)調(diào)用,一個OK則OK
BroadcastCluster BroadcastClusterInvoker 逐個調(diào)用invoker,全可用才可用
AvailableCluster AvailableClusterInvoker 哪個能用就用那個
MergeableCluster MergeableClusterInvoker 按組合并返回結(jié)果

7.2 智能容錯之負(fù)載均衡

Dubbo中一般有4種負(fù)載均衡策略。

  1. RandomLoadBalance:加權(quán)隨機(jī),它的算法思想簡單。假設(shè)有一組服務(wù)器 servers = [A, B, C],對應(yīng)權(quán)重為 weights = [5, 3, 2],權(quán)重總和為10?,F(xiàn)把這些權(quán)重值平鋪在一維坐標(biāo)值上,[0, 5) 區(qū)間屬于服務(wù)器 A,[5, 8) 區(qū)間屬于服務(wù)器 B,[8, 10) 區(qū)間屬于服務(wù)器 C。接下來通過隨機(jī)數(shù)生成器生成一個范圍在 [0, 10) 之間的隨機(jī)數(shù),然后計算這個隨機(jī)數(shù)會落到哪個區(qū)間上。默認(rèn)實現(xiàn)。
  2. LeastActiveLoadBalance:最少活躍數(shù)負(fù)載均衡,選擇現(xiàn)在活躍調(diào)用數(shù)最少的提供者進(jìn)行調(diào)用,活躍的調(diào)用數(shù)少說明它現(xiàn)在很輕松,而且活躍數(shù)都是從 0 加起來的,來一個請求活躍數(shù)+1,一個請求處理完成活躍數(shù)-1,所以活躍數(shù)少也能變相的體現(xiàn)處理的快。
  3. RoundRobinLoadBalance:加權(quán)輪詢負(fù)載均衡,比如現(xiàn)在有兩臺服務(wù)器 A、B,輪詢的調(diào)用順序就是 A、B、A、B,如果加了權(quán)重,A 比B 的權(quán)重是2:1,那現(xiàn)在的調(diào)用順序就是 A、A、B、A、A、B。
  4. ConsistentHashLoadBalance:一致性 Hash 負(fù)載均衡,將服務(wù)器的 IP 等信息生成一個 hash 值,將hash 值投射到圓環(huán)上作為一個節(jié)點,然后當(dāng) key 來查找的時候順時針查找第一個大于等于這個 key 的 hash 值的節(jié)點。一般而言還會引入虛擬節(jié)點,使得數(shù)據(jù)更加的分散,避免數(shù)據(jù)傾斜壓垮某個節(jié)點。如下圖 Dubbo 默認(rèn)搞了 160 個虛擬節(jié)點。圖片

7.3 智能容錯之服務(wù)目錄

關(guān)于 服務(wù)目錄Directory 你可以理解為是相同服務(wù)Invoker的集合,核心是RegistryDirectory類。具有三個功能。

從注冊中心獲得invoker列表。

監(jiān)控著注冊中心invoker的變化,invoker的上下線。

刷新invokers列表到服務(wù)目錄。

7.4 智能容錯之服務(wù)路由

服務(wù)路由其實就是路由規(guī)則,它規(guī)定了服務(wù)消費者可以調(diào)用哪些服務(wù)提供者。條件路由規(guī)則由兩個條件組成,分別用于對服務(wù)消費者和提供者進(jìn)行匹配。比如有這樣一條規(guī)則:

  1. host = 10.20.153.14 => host = 10.20.153.12 

該條規(guī)則表示 IP 為 10.20.153.14 的服務(wù)消費者只可調(diào)用 IP 為 10.20.153.12 機(jī)器上的服務(wù),不可調(diào)用其他機(jī)器上的服務(wù)。條件路由規(guī)則的格式如下:

  1. [服務(wù)消費者匹配條件] => [服務(wù)提供者匹配條件] 

如果服務(wù)消費者匹配條件為空,表示不對服務(wù)消費者進(jìn)行限制。如果服務(wù)提供者匹配條件為空,表示對某些服務(wù)消費者禁用服務(wù)。

8、設(shè)計RPC

通讀下Dubbo的大致實現(xiàn)方式后其實就可以依葫蘆畫瓢了,一個RPC框架大致需要下面這些東西:

服務(wù)的注冊跟發(fā)現(xiàn)的搞一個吧,你可以用ZooKeeper或者Redis來實現(xiàn)。

接下來consumer發(fā)起請求的時候你的面向接口編程啊,用到動態(tài)代理來實現(xiàn)調(diào)用。

多個provider提供相同服務(wù)你的用到LoadBalance啊。

最終選擇一個機(jī)器后你的約定好通信協(xié)議啊,如何進(jìn)行序列化跟反序列化呢?

底層就用現(xiàn)成的高性能Netty框架 NIO模式實現(xiàn)唄。

服務(wù)開啟后的有monitor啊。

PS :

感覺沒啥特別好寫的,因為人Dubbo官方文檔啥都有,你說你英文看不懂,那中文總該看得懂了吧。

參考

Dubbo面試題:https://sowhat.blog.csdn.net/article/details/71191035

Adaptive講解:https://blog.csdn.net/weixin_33967071/article/details/92608993 Dubbo視頻:https://b23.tv/KVk0xo

Dubbo demo:https://mp.weixin.qq.com/s/FPbu8rFOHyTGROIV8XJeTA doExportUrlsFor1Protocol詳解:https://www.cnblogs.com/hzhuxin/p/7993860.html

 

責(zé)任編輯:武曉燕 來源: sowhat1412
相關(guān)推薦

2023-02-22 14:50:59

技術(shù)AI

2024-03-06 13:56:00

項目awaitpromise

2025-02-07 15:01:49

Promise數(shù)組前端

2014-06-17 09:51:57

Docker

2024-01-02 16:16:34

Promise前端

2019-11-27 11:06:30

災(zāi)難DDoS勒索軟件

2009-03-26 09:39:16

CSS網(wǎng)頁布局

2010-09-27 13:41:49

TCP IP故障問題

2023-08-09 14:01:55

2022-11-04 15:37:04

產(chǎn)品策略開發(fā)競爭

2012-10-15 09:36:23

2023-05-18 14:06:51

人工智能AI

2010-08-31 10:49:16

CSS網(wǎng)頁布局

2019-10-18 15:16:10

Redis數(shù)據(jù)庫并發(fā)

2009-12-04 15:33:42

安裝Windows 7

2022-12-01 16:53:27

NPM技巧

2023-10-10 10:27:37

DevOps

2023-02-27 09:08:10

IT文化步驟

2017-04-20 12:51:28

2017-01-05 09:59:45

點贊
收藏

51CTO技術(shù)棧公眾號