【分佈式系統遨遊】分佈式通訊

今天咱們來討論分佈式通訊技術。java

爲何須要分佈式通訊

咱們以前在講分佈式資源調度的時候,把分佈式系統中的各個節點與操做系統的進程作了類比。咱們知道,操做系統的進程之間因爲須要數據的交換,是須要進程通訊機制的。那麼同理,分佈式系統之間一樣須要通訊。在業務層面,每一個分佈式系統通常都承載着一個微服務,因此,微服務之間也必定是須要通訊的。好比,咱們各條業務線均須要查詢用戶中心微服務的數據等等。咱們經常使用的通訊方式有三種:RPC、發佈-訂閱、消息隊列。算法

RPC

在傳統的B/S模式中,服務端會對外暴露接口,而後客戶端經過調用這個接口來完成兩者之間的通訊。那麼在分佈式系統中,咱們一樣也能夠採用這種模式。可是,B/S 架構是基於 HTTP 協議實現的,每次調用接口時,都須要先進行 HTTP 請求。這樣既繁瑣又浪費時間,不適用於有低時延要求的大規模分佈式系統,因此遠程調用的實現大多采用更底層的網絡通訊協議。咱們先用一張圖俯瞰一下RPC的架構:

在這裏,訂單系統進程並不須要知道底層是如何傳輸的,在用戶眼裏,遠程過程調用和調用一次本地服務沒什麼不一樣。這,就是 RPC 的核心。即圖中的第3步和第8步,對咱們調用方是透明的。與咱們常用的接口調用不一樣,圖中的網絡通訊基本是基於TCP協議本身封裝的一些協議。這樣作能夠約定通訊雙方的數據格式,從而讓客戶端封包和服務端解包更加快速,更加適用於分佈式系統。這裏的通訊協議封裝可參考Redis的RESP協議與FastCGI協議。spring

RPC的典型實現 - Dubbo

假設咱們要本身去實現一個RPC通訊框架,咱們應該如何實現呢?假如咱們用4個調用方與4個服務提供方,咱們該如何管理他們呢?

首先,咱們最容易想到的,就是服務提供方爲服務調用方,提供相關的SDK,服務調用方直接引入SDK便可發起RPC調用請求,而SDK內部具體是利用什麼協議,調用方並不關心。這是一種方案。可是,隨着服務提供方和服務調用方愈來愈多,服務調用關係會越發複雜。假設服務提供方有 n個, 服務調用方有 m 個,則調用關係可達 n*m,這會致使系統的通訊量很大,SDK就顯得力不從心了。此時,你可能會想到,在計算機領域,全部的問題均可以經過增長一箇中間層來解決。那麼,咱們爲何不使用一個服務註冊中心來進行統一管理呢,這樣調用方只須要到服務註冊中心去查找相應的地址便可,並不關心有多少個服務提供方,從而實現了服務調用方與服務提供方的解耦:

Dubbo 在引入服務註冊中心的基礎上,又加入了監控中心組件(用來監控服務的調用狀況,以方便進行服務治理),實現了一個 RPC 框架。以下圖所示,Dubbo 的架構主要包括 4 部分:數據庫

  • 服務提供方。服務提供方會向服務註冊中心註冊本身提供的服務。
  • 服務註冊中心。服務註冊與發現中心,負責存儲和管理服務提供方註冊的服務信息和服務調用方訂閱的服務類型等。
  • 服務調用方。根據服務註冊中心返回的服務所在的地址列表,經過遠程調用訪問遠程服務。
  • 監控中心。統計服務的調用次數和調用時間等信息的監控中心,以方便進行服務管理或服務失敗分析等。

下面是Dubbo官網給出的一個調用方的Demo。首先是對須要調用的服務在服務註冊中心的地址進行配置:apache

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- consumer's application name, used for tracing dependency relationship (not a matching criterion),
    don't set it same as provider -->
    <dubbo:application name="demo-consumer"/>
    <!-- use multicast registry center to discover service -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>
    <!-- generate proxy for the remote service, then demoService can be used in the same way as the
    local regular interface -->
    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
</beans>

而後,在業務代碼中調用剛剛配置好的服務提供方地址便可。咱們再也不須要SDK了:segmentfault

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.dubbo.demo.DemoService;
 
public class Consumer {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"META-INF/spring/dubbo-demo-consumer.xml"});
        context.start();
        // Obtaining a remote service proxy
        DemoService demoService = (DemoService)context.getBean("demoService");
        // Executing remote methods
        String hello = demoService.sayHello("world");
        // Display the call result
        System.out.println(hello);
    }
}

發佈-訂閱

發佈-訂閱的思想在生活中隨處可見。好比咱們吃雞的時候,通常是4我的組隊開黑,咱們在分佈式系統中能夠比做4個節點。舉一個至關經典的場景,好比我跳了機場,資源不少,有5.56的子彈、7.62的子彈等。因而我就和隊友說,我多5.56和7.62子彈,誰須要的話和我說一下。可是有些隊友去打野了,就會比較窮,他們就會和我說,我要5.56子彈或者我要7.62子彈。而後,我就會找到這個窮隊友,而後把相應的5.56和7.62子彈分給他們,這樣就完成了一次發佈-訂閱的流程。其中,」我就和隊友說,我多5.56和7.62子彈,誰須要的話和我說一下「,這個就是將」我多子彈「這個消息事件發佈出去,而後很窮的隊友說」我須要xxx子彈「,就至關於訂閱我發佈的這個消息事件,而後我就會把子彈給到他們,這個子彈就至關於咱們的消息,這樣就完成了一次發佈-訂閱模型的通訊:

其中,生產者能夠發送消息到中心,而消息中心一般以主題(Topic)進行劃分,每條消息都會有相應的主題,它表明該條消息的類型。訂閱該主題的全部消費者都可得到該消息進行消費。這裏咱們的5.56子彈與7.62子彈,就至關於兩個topic,咱們能夠訂閱其中一個topic,來得到咱們須要的子彈類型。網絡

發佈-訂閱的典型實現 - Kafka

Kafka是一個典型的發佈訂閱消息系統,其系統架構也是包括生產者、消費者和消息中心三部分:

在Kafka中,爲了解決消息存儲的負載均衡和系統可靠性問題,因此引入了主題(topic)和分區(partition)的概念。topic的概念咱們剛纔講過了,它是一個邏輯概念,指的是消息類型或數據類型。那麼分區是基於topic而言的。一個topic的內容能夠被劃分紅多個分區,而這些分區又分佈在不一樣的集羣節點上,每一個分區的數據內容依賴數據同步機制,來確保每一個分區內部存儲數據的一致性:

每一個broker就表明了一個集羣中的物理節點。經過分區機制,咱們避免了「將數據都放在一個籃子裏」,將數據分散在不一樣的broker機器上,提升了系統的數據可靠性,且實現了負載均衡。
在圖中,還有一點不同的地方就是,有兩個消費者組成了一個消費組。那麼爲何要引入消費組呢?咱們知道,在消息過多的狀況下,單個消費者消費能力有限時,會致使消費效率太低,從而致使 Broker 存儲溢出,從而不得不丟棄一部分消息。Kafka爲了解決這個問題,因此引入了消費組,提升了消費的速度。
在Kafka中,除了基本的三要素以外,還使用了Zookeeper。ZooKeeper是一個提供了分佈式服務協同能力的第三方組件,用來協調和管理整個集羣中的Broker和Consumer,實現了Broker 和Consumer的解耦,併爲系統提供可靠性保證。Consumer 和 Broker 啓動時均會向 ZooKeeper 進行註冊,由 ZooKeeper 進行統一管理和協調。
ZooKeeper 中會存儲一些元數據信息,好比對於 Broker,會存儲主題對應哪些分區(Partition),每一個分區的存儲位置等;對於 Consumer,會存儲消費組(Consumer Group)中包含哪些 Consumer,每一個 Consumer 會負責消費哪些分區等。架構

消息隊列

消息隊列與發佈-訂閱模型比較類似,可是也有一些不一樣之處。接着咱們以前吃雞的例子來講,消息隊列並不關心誰須要什麼子彈,只把本身多的資源放到某個位置,讓隊友來拿就行了。若是隊友有須要,自取便可。消息隊列並不直接把資源分配到某個具體消費者,只負責發佈到消息隊列中,而後消費者各取所需。最典型的一個場景就是異步通訊。
舉個例子,用戶註冊須要寫數據庫、發送郵件,按照最簡單的同步通訊方式,那麼從用戶提交註冊到收到響應,須要等系統完成這兩個步驟,纔會給用戶返回註冊成功。若是發送郵件耗時很是之長,那麼用戶就得一直等下去:

以下圖所示,若是引入消息隊列,做爲註冊消息寫入數據庫和發送郵件、短信這三個組件間的中間通訊者,那麼這三個組件就能夠實現異步通訊、異步執行:

即用戶只須要在寫入數據庫以後,寫入發送郵件的消息隊列便可返回註冊成功,而並不須要等待真正的去發送郵件以後纔會返回。因此,咱們解除了註冊與發送郵件兩種操做之間的耦合,大大提升了註冊的響應速度。那你可能會問,若是發送郵件失敗了怎麼辦?咱們通常會在業務層寫一些重試邏輯,確保郵件發送成功以後,纔算成功消費。而隊列通常也會有持久化機制,確保消息不會丟失。
除了將同步轉化爲異步,消息隊列在高併發系統中也承擔着流量削峯的做用。對於流量控制,還有漏桶和令牌桶算法,感興趣的讀者能夠進一步去了解。併發

下期預告

【分佈式系統遨遊】分佈式計算app

關注咱們

歡迎對本系列文章感興趣的讀者訂閱咱們的公衆號,關注博主下次不迷路~

Nosay

相關文章
相關標籤/搜索