Dubbo 入門-細說分散式與集群

什麼是Dubbo

Dubbo是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向介面的遠程方法調用,智慧容錯和負載均衡,以及服務自動註冊和發現。

什麼是RPC

RPC全稱(Remote Procedure Call)遠程過程調用

過程指的是某個程式碼片段的執行,遠程調用則意味著我們可以在其他進程,甚至其他機器上去調用這段程式碼,當然也能獲取到其執行後的返回值,按照這個定義,我們請求某個http地址得到相應數據其實也算一次RPC,但是這樣的方式太過麻煩,(數據要先打包成http請求格式,在調用相關的請求庫,拿到的結果也是文本格式的需要在進行轉換),執行效率,和開發效率相比RPC則低一些;

我們需要一種更簡單的方式來完成分散式開發中的RPC環節,這也是Dubbo的核心所在,有多簡單呢? 調用遠程伺服器上的某個服務時就像是調用本地的某個方法一樣簡單,就像下面這樣

為什麼需要rpc

RPC是用來實現分散式構架的基石,分散式構架將同一個系統中的不同模組拆分到不同的子系統中,而子系統又分布在不同的伺服器上,這時就需要RPC在來完成子系統之間的相互訪問;

可以這麼說分散式少不了RPC,RPC也要在分散式系統中才能發揮其核心價值;

rpc的實現原理

毫無以為底層肯定是要通過socket來進行網路通訊的,但是如何能夠直接調用另一個機器上的方法呢?

服務消費方(client)調用以本地調用方式調用服務;

2)client stub接收到調用後負責將方法、參數等組裝成能夠進行網路傳輸的消息體;

3)client stub找到服務地址,並將消息發送到服務端;

4)server stub收到消息後進行解碼;

5)server stub根據解碼結果調用本地的服務;

6)本地服務執行並將結果返回給server stub;

7)server stub將返回結果打包成消息並發送至消費方;

8)client stub接收到消息,並進行解碼;

9)服務消費方得到最終結果。

當然傳遞的參數或返回值是某個Java對象時則還需要對其進行序列化與反序列化

分散式與集群

集群:

集群構架是將相同的處理邏輯進行複製(複製一份源程式碼),創建出一組具備相同功能的服務集合,集群中每個服務都能夠獨立的完成用戶的請求,它們之間基本上不需要互相通訊,也就用不上RPC了;

分散式:

分散式指的是將一個系統拆分為多個獨立的子系統,部署在不同的機器上;

在處理任務時會將一個任務拆分成若干子任務,分發給不同的子系統處理,每個子系統僅能處理一部分任務,通常一個完整的任務包含多個處理步驟,例如用戶要購買某個商品,需要先創建訂單,然後修改庫存,假設修改庫存的服務由另一個伺服器提供這時候RPC就閃亮登場了;

可以發現分散式與集群在底層構架上完全不同,所以要將一個原本集群的系統重構為分散式的話,則需要大量的修改,所以若系統後期存在高並發的需求,則可以在項目初期就採用分散式構架來搭建;

分散式是必要的嗎?

分散式的優缺點:

  • 可將原本串列的任務變為並行執行(沒有前後依賴),提高計算速度
  • 提高可用性,由於系統分布在不同的計算節點上,其中某個節點失效不會對整個系統產生太大的影響
  • 各個子系統獨立運行,極大的降低了系統的耦合度,使得各個子系統的擴展性和可業務功能的維護性提高
  • 因為模組化,所以系統模組重用度更高(系統級別)
  • 技術開放,多樣化,完全可以使用其他語言,其他平台來開發某個子系統
  • 更有效的利用硬體資源

缺點:

  • 因為需要走RPC,響應時間變長

  • 系統構架更加複雜,運維工作麻煩

  • 需要進行服務管理和調度
  • 測試和調試更複雜
  • 公共模組無法復用(程式碼級別)

需要強調的是:分散式和集群並不是只能二選一,在高並發下場景下還可以給壓力大的節點組建集群;
分散式與微服務:

分散式系統是多個處理機通過通訊線路互聯而構成的鬆散耦合的系統,是一個更寬泛的概念;

微服務從結構上來看也屬於分散式,微服務強調的是將某個功能完完全全的獨立出來,徹底的解開耦合;

RPC和微服務才算是同一級別的東西,即實現分散式可以使用rpc也可以使用微服務;

系統構架演進:

image

SOA是解決海量並發訪問的終極解決方案,無論是採用RPC還是微服務

為什麼需要Dubbo:

引用官方原話:

在大規模服務化之前,應用可能只是通過 RMI 或 Hessian 等工具,簡單的暴露和引用遠程服務,通過配置服務的URL地址進行調用,通過 F5 等硬體進行負載均衡。

當服務越來越多時,服務 URL 配置管理變得非常困難,F5 硬體負載均衡器的單點壓力也越來越大。此時需要一個服務註冊中心,動態地註冊和發現服務,使服務的位置透明。並通過在消費方獲取服務提供方地址列表,實現軟負載均衡和 Failover,降低對 F5 硬體負載均衡器的依賴,也能減少部分成本。

當進一步發展,服務間依賴關係變得錯蹤複雜,甚至分不清哪個應用要在哪個應用之前啟動,架構師都不能完整的描述應用的架構關係。 這時,需要自動畫出應用間的依賴關係圖,以幫助架構師理清關係。

接著,服務的調用量越來越大,服務的容量問題就暴露出來,這個服務需要多少機器支撐?什麼時候該加機器? 為了解決這些問題,第一步,要將服務現在每天的調用量,響應時間,都統計出來,作為容量規劃的參考指標。其次,要可以動態調整權重,在線上,將某台機器的權重一直加大,並在加大的過程中記錄響應時間的變化,直到響應時間到達閾值,記錄此時的訪問量,再以此訪問量乘以機器數反推總容量。

簡單的說,Dubbo不僅僅是實現了RPC,同時提供了整套分散式服務的管理方案; 包括

  • 服務註冊與發現

  • 負載均衡
  • 流量調度
  • 提供可視化的服務治理工具,和運維工具

構架及服務調用流程

dubbo-architucture
舉例

角色:

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

調用過程:

  1. 服務容器負責啟動,載入,運行服務提供者。
  2. 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
  3. 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
  4. 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
  5. 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一台提供者進行調用,如果調用失敗,再選另一台調用。
  6. 服務消費者和提供者,在記憶體中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。

hello Dubbo

1.創建鋪Maven工程DubboDemo

2.在當前工程下創建provider模組

3.為provider添加依賴

 <dependency>              <groupId>junit</groupId>              <artifactId>junit</artifactId>              <version>4.12</version>              <scope>test</scope>          </dependency>          <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.13</version>          </dependency>          <dependency>              <groupId>com.101tec</groupId>              <artifactId>zkclient</artifactId>              <version>0.11</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>

4.dubbo發布服務的單位是介面,所以我們需要創建一個服務介面,在消費端也需要同樣的介面來產生代理對象,為了抽取公共部分程式碼,可以新建一個模組然後讓提供方和消費方依賴這個項目從而找到需要的介面

在公共模組中創建介面:

package com.yyh.service;    public interface HelloService {      String helloMan(String name);  }

​ 在pom中和依賴剛才新建的項目

<dependency>    <groupId>org.example</groupId>    <artifactId>hello_Interface</artifactId>    <version>1.0-SNAPSHOT</version>  </dependency>

5.在provider中創建實現類

package com.yyh.service.impl;    import com.yyh.service.HelloService;    public class HelloServiceImpl implements HelloService {      public String helloMan(String name) {          return "hello: "+name;      }  }

6.編寫提供方配置文件

<?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-service">        <dubbo:parameter key="qos.enable" value="true"/>      </dubbo:application>      <!--dubbo這個服務所要暴露的服務地址所對應的註冊中心-->      <dubbo:registry address="N/A"/>      <!--當前服務發布所依賴的協議;webserovice、Thrift、Hessain、http-->      <dubbo:protocol name="dubbo" port="20880"/>      <!--服務發布的配置,需要暴露的服務介面-->      <dubbo:service interface="com.yyh.service.HelloService" ref="helloService"/>      <!--Bean bean定義-->      <bean id="helloService" class="com.yyh.service.impl.HelloServiceImpl"/>  </beans>

7.啟動服務

import org.springframework.context.support.ClassPathXmlApplicationContext;    import java.io.IOException;    public class Runner {      public static void main(String[] args) throws IOException {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:provider.xml");          context.start();          System.out.println("send anyket to exit");          System.in.read();      }  }

8.為了方便調試我們可以提供一個日誌配置在資源目錄下名為log4j.properties

log4j.rootLogger=info,console  log4j.appender.console=org.apache.log4j.ConsoleAppender  log4j.appender.console.Target = System.out  log4j.appender.console.layout=org.apache.log4j.PatternLayout  log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r]-[%p] %m%n

9.創建消費端模組

在pom中引入同樣的依賴

10.創建配置文件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="hello-consumer"/>      <!--註冊中心        -->      <dubbo:registry address="N/A"/>      <!--創建一個代理對象到容器中    -->      <dubbo:reference id="helloService" interface="com.yyh.service.HelloService"                       url="dubbo://192.168.2.5:20880/com.yyh.service.HelloService"/>    </beans>

11.運行測試:

import com.yyh.service.HelloService;  import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Runner {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");          HelloService helloService = (HelloService) context.getBean("helloService");          String jerry = helloService.helloMan("jerry");          System.out.println(jerry);      }  }

若輸出hello jerry則表示調用服務成功了;