淺析如何基于ZooKeeper實現(xiàn)高可用架構(gòu)|得物技術(shù)

原創(chuàng) 得物技術(shù)-胡強忠

1. 背景

對于zookeeper隘蝎,大家都比較熟悉,在Kafka巨柒、HBase、Dubbo中都有看到過他的身影柠衍,那zookeeper到底是什么洋满,都能做些什么呢?今天我們一起來了解下珍坊。

2. ZooKeeper簡介

2.1 概述

簡單理解牺勾,ZooKeeper 是分布式應(yīng)用程序的分布式開源協(xié)調(diào)服務(wù),封裝了分布式架構(gòu)中核心和主流的需求和功能阵漏,并提供一系列簡單易用的接口提供給用戶使用驻民。分布式應(yīng)用程序可以根據(jù)zookeeper實現(xiàn)分布式鎖、分布式集群的集中式元數(shù)據(jù)存儲履怯、Master選舉回还、分布式協(xié)調(diào)和通知等。下圖是官網(wǎng)上zookeeper的架構(gòu)簡圖:

image.png

從zk的架構(gòu)圖中也可以了解到叹洲,zk本身就是一個分布式的柠硕、高可用的系統(tǒng)。有多個server節(jié)點疹味,其中有一個是leader節(jié)點仅叫,其他的是follower節(jié)點帜篇,他們會從leader節(jié)點中復(fù)制數(shù)據(jù)糙捺。客戶端節(jié)點連接單個zk服務(wù)器笙隙,并維護(hù)一個TCP連接洪灯,通過該連接發(fā)送請求、獲取響應(yīng)以及監(jiān)聽事件并發(fā)送心跳竟痰,如果與服務(wù)器的連接中斷签钩,就會連接到另外的服務(wù)器掏呼。

2.2 技術(shù)特點

zookeeper有以下幾個特點:

  • 集群部署:一般是3~5臺機器組成一個集群,每臺機器都在內(nèi)存保存了zk的全部數(shù)據(jù)铅檩,機器之間互相通信同步數(shù)據(jù)憎夷,客戶端連接任何一臺機器都可以。

  • 順序一致性:所有的寫請求都是有序的昧旨;集群中只有l(wèi)eader機器可以寫拾给,所有機器都可以讀,所有寫請求都會分配一個zk集群全局的唯一遞增編號:zxid兔沃,用來保證各種客戶端發(fā)起的寫請求都是有順序的蒋得。

  • 原子性:要么全部機器成功,要么全部機器都不成功乒疏。

  • 數(shù)據(jù)一致性:無論客戶端連接到哪臺節(jié)點额衙,讀取到的數(shù)據(jù)都是一致的纳猪;leader收到了寫請求之后都會同步給其他機器贱鼻,保證數(shù)據(jù)的強一致,你連接到任何一臺zk機器看到的數(shù)據(jù)都是一致的瓷蛙。

  • 高可用:如果某臺機器宕機械哟,會保證數(shù)據(jù)不丟失疏之。集群中掛掉不超過一半的機器,都能保證集群可用暇咆。比如3臺機器可以掛1臺锋爪,5臺機器可以掛2臺。

  • 實時性:一旦數(shù)據(jù)發(fā)生變更爸业,其他節(jié)點會實時感知到其骄。

  • 高性能:每臺zk機器都在內(nèi)存維護(hù)數(shù)據(jù),所以zk集群絕對是高并發(fā)高性能的扯旷,如果將zk部署在高配置物理機上拯爽,一個3臺機器的zk集群抗下每秒幾萬請求是沒有問題的。

  • 高并發(fā):高性能決定的钧忽,主要是基于純內(nèi)存數(shù)據(jù)結(jié)構(gòu)來處理毯炮,并發(fā)能力是很高的,只有一臺機器進(jìn)行寫耸黑,但是高配置的物理機桃煎,比如16核32G,可以支撐幾萬的寫入QPS大刊。所有機器都可以讀为迈,選用3臺高配機器的話,可以支撐十萬+的QPS『可參考官網(wǎng)「基于3.2版本做的性能壓測【1】」

2.3 技術(shù)本質(zhì)

image.png

zookeeper的技術(shù)本質(zhì)決定了搜锰,一些開源項目為什么要使用zk來實現(xiàn)自己系統(tǒng)的高可用。zk是基于zab協(xié)議來實現(xiàn)的分布式一致性的耿战,他的核心就是圖中的Atomic Broadcast蛋叼,通過原子廣播的能力,保持所有服務(wù)器的同步剂陡,來實現(xiàn)了分布式一致性鸦列。

zookeeper算法即zab算法或者叫zab協(xié)議,需注意zab并不是paxos鹏倘,zab的核心是為了實現(xiàn)primary-back systems這種架構(gòu)模式薯嗤,而paxos實際上是叫 state machine replication,就是狀態(tài)復(fù)制機。這里簡單對比下纤泵。

  • primary-back systems:leader節(jié)點接受寫請求骆姐,先寫到自己的本地機器,然后通過原子協(xié)議或其他的方式捏题,將結(jié)果復(fù)制到其他的系統(tǒng)玻褪。

  • state machine replication:沒有一個明顯的leader節(jié)點。寫的時候公荧,把接收到的命令記錄下來带射,然后把命令復(fù)制給各個節(jié)點,然后各個節(jié)點再去執(zhí)行命令循狰,應(yīng)用到自己的狀態(tài)機里面窟社。

關(guān)于一致性算法這里不做具體對比,后續(xù)會詳細(xì)說下常見的分布式集群算法绪钥。

2.3.1 ZAB工作流程

ZAB協(xié)議灿里,即ZooKeeper Atomic Broadcas。集群啟動自動選舉一個Leader出來程腹,只有Leader是可以寫的匣吊,F(xiàn)ollower是只能同步數(shù)據(jù)和提供數(shù)據(jù)的讀取,Leader掛了寸潦,F(xiàn)ollower可以繼續(xù)選舉出來Leader色鸳。這里可以看下zab的工作流程:

image.png

Leader收到寫請求,就把請求廣播給所有的follower见转,每一個消息廣播的時候命雀,都是按照2PC思想,先是發(fā)起事務(wù)Proposal的廣播池户,就是事務(wù)提議咏雌,在發(fā)起一個事務(wù)proposal之前,leader會分配一個全局唯一遞增的事務(wù)id校焦,zxid赊抖,通過這個可以嚴(yán)格保證順序。每個follower收到一個事務(wù)proposal之后寨典,就立即寫入本地磁盤日志中氛雪,寫入成功之后返回一個ack給leader,然后過半的follower都返回了ack之后耸成,leader會推送commit消息給全部follower报亩,Leader自己也會進(jìn)行commit操作,commit之后,數(shù)據(jù)才會寫入znode井氢,然后這個數(shù)據(jù)可以被讀取到了弦追。

如果突然leader宕機了,會進(jìn)入恢復(fù)模式花竞,重新選舉一個leader劲件,只要過半的機器都承認(rèn)你是leader,就可以選舉出來一個新的leader约急,所以zookeeper很重要的一點是宕機的機器數(shù)量小于一半零远,他就可以正常工作。這里不具體展開詳細(xì)的選舉和消息丟棄的邏輯厌蔽。

從上述的工作過程牵辣,可以看出zookeeper并不是強制性的,leader并不是保證一條數(shù)據(jù)被全部follower都commit了才會讓客戶端讀到奴饮,過程中可能會在不同的follower上讀取到不一致的數(shù)據(jù)纬向,但是最終所有節(jié)點都commit完成后會一致性的。zk官方給自己的定義是:順序一致性戴卜。

從上述的這些技術(shù)特點上罢猪,我們也可以看出,為什么zookeeper只能是小集群部署叉瘩,而且是適合讀多寫少的場景膳帕。想象一下,如果集群中有幾十個節(jié)點薇缅,其中1臺是leader危彩,每次寫請求,都要跟所有節(jié)點同步泳桦,并等過半的機器ack之后才能提交成功汤徽,這個性能肯定是很差的。舉個例子灸撰,如果使用zookeeper做注冊中心谒府,大量的服務(wù)上線拼坎、注冊、心跳的壓力完疫,如果達(dá)到了每秒幾萬甚至上十萬的場景泰鸡,zookeeper的單節(jié)點寫入是扛不住那么大壓力的。如果這是kafka或者其他中間件共用了一個zookeeper集群壳鹤,那基本就都癱瘓了盛龄。

2.3.2 ZooKeeper角色

上述的場景中已經(jīng)提到了leader和follower,既然提到了性能問題芳誓,就額外說下余舶,除了leader和follower,zookeeper還有一個角色是:observer锹淌。

observer節(jié)點是不參與leader選舉的匿值;他也不參與zab協(xié)議同步時,過半follower ack的環(huán)節(jié)赂摆。他只是單存的接收數(shù)據(jù)千扔,同步數(shù)據(jù),提供讀服務(wù)库正。這樣zookeeper集群曲楚,可以通過擴展observer節(jié)點,線性提升高并發(fā)查詢的能力 褥符。

2.4 Znode數(shù)據(jù)模型

如果想使用好zk的話龙誊,必須要了解下他的數(shù)據(jù)模型。雖然zookeeper的實現(xiàn)比較復(fù)雜喷楣,但是數(shù)據(jù)結(jié)構(gòu)還是比較簡單的趟大。如下圖所示,zookeeper的數(shù)據(jù)結(jié)構(gòu)是一個類型文件系統(tǒng)的樹形目錄分層結(jié)構(gòu)铣焊,它是純內(nèi)存保存的逊朽。基于這個目錄結(jié)構(gòu)曲伊,可以根據(jù)自己的需要叽讳,設(shè)計的具體的概念含義。ZooKeeper 的層次模型稱作 Data Tree坟募,Data Tree 的每個節(jié)點叫作 znode岛蚤。如下圖所示:

ZNode在我們應(yīng)用中是主要訪問的實體,有些特點這里提出來懈糯,說一下:

  • 【W(wǎng)atches】 監(jiān)聽

zookeeper支持 watch 的概念涤妒。客戶端可以在 znode 上設(shè)置監(jiān)聽赚哗,當(dāng) znode 發(fā)生變化時她紫,watch 將被觸發(fā)并移除硅堆。當(dāng) watch 被觸發(fā)時,客戶端會收到一個數(shù)據(jù)包贿讹,說明 znode 已更改渐逃,這個在分布式系統(tǒng)的協(xié)調(diào)中是很有用的一個功能。

  • 【Data Access】 數(shù)據(jù)訪問

存儲在命名空間中每個 znode 的數(shù)據(jù)的讀取和寫入都是原子的围详。讀取操作獲取與 znode 關(guān)聯(lián)的所有數(shù)據(jù),寫入操作會替換所有數(shù)據(jù)祖屏。每個節(jié)點都有一個訪問控制列表 (ACL)助赞,用于限制誰可以做什么。

  • 【Ephemeral Nodes】臨時節(jié)點

ZooKeeper 也有臨時節(jié)點的概念袁勺。只要創(chuàng)建 znode 的會話處于活動狀態(tài)雹食,這些 znode 就存在。當(dāng)會話結(jié)束時期丰,znode 被刪除群叶。臨時 znode 不允許有子節(jié)點。

  • 持久節(jié)點

zooKeeper除了臨時節(jié)點還有持久節(jié)點钝荡,客戶端斷開連接街立,或者集群宕機,節(jié)點也會一直存在埠通。

  • 【Sequence Nodes】 順序節(jié)點

創(chuàng)建 znode 時赎离,可以 在path路徑末尾附加一個單調(diào)遞增的計數(shù)器。這個計數(shù)器對于父 znode 是唯一的端辱,是全局遞增的序號梁剔。

3. ZooKeeper的應(yīng)用

對zookeeper有了一定的了解之后,我們看下他有哪些應(yīng)用場景舞蔽。前面的背景中也提到過荣病,在一些常見的開源項目中,都看到過zk的身影渗柿,那zk在這些開源項目中是如何使用的呢个盆?

3.1 典型的應(yīng)用場景

(1)元數(shù)據(jù)管理:Kafka、Canal朵栖,本身都是分布式架構(gòu)砾省,分布式集群在運行,本身他需要一個地方集中式的存儲和管理分布式集群的核心元數(shù)據(jù)混槐,所以他們都選擇把核心元數(shù)據(jù)放在zookeeper中编兄。

Dubbo:使用zookeeper作為注冊中心、分布式集群的集中式元數(shù)據(jù)存儲

HBase:使用zookeeper做分布式集群的集中式元數(shù)據(jù)存儲

(2)分布式協(xié)調(diào):如果有節(jié)點對zookeeper中的數(shù)據(jù)做了變更声登,然后zookeeper會反過來去通知其他監(jiān)聽這個數(shù)據(jù)的節(jié)點狠鸳,告訴它這個數(shù)據(jù)變更了揣苏。

kafka:通過zookeeper解決controller的競爭問題。kafka有多個broker件舵,多個broker會競爭成為一個controller的角色卸察。如果作為controller的broker掛掉了,此時他在zk里注冊的一個節(jié)點會消失铅祸,其他broker瞬間會被zookeeper反向通知這個事情坑质,繼續(xù)競爭成為新的controller,這個就是非常經(jīng)典的一個分布式協(xié)調(diào)的場景临梗。

(3)Master選舉 -> HA架構(gòu)

Canal:通過zookeeper解決Master選舉問題涡扼,來實現(xiàn)HA架構(gòu)

HDFS:Master選舉實現(xiàn)HA架構(gòu),NameNode HA架構(gòu)盟庞,部署主備兩個NameNode吃沪,只有一個人可以通過zookeeper選舉成為Master,另外一個作為backup什猖。

(4)分布式鎖

一般用于分布式的業(yè)務(wù)系統(tǒng)中票彪,控制并發(fā)寫操作。不過實際使用中不狮,使用zookeeper做分布式鎖的案例比較少降铸,大部分都是使用的redis.

3.2 開源產(chǎn)品使用zk的情況

下圖是有關(guān)Paxos Systems Demystified的論文中,常見的開源產(chǎn)品使用的一致性算法系統(tǒng)摇零,可以看到除了google系的產(chǎn)品用paxos算法或他們自己的chubby服務(wù)外垮耳,開源的系統(tǒng)大部分都使用zookeeper來實現(xiàn)高可用的。

image.png

4. 實踐-通過ZooKeeper來實現(xiàn)高可用

了解了zookeeper的一些技術(shù)特性及常見的使用場景后遂黍,考慮一個問題:我們平時在工作中终佛,大部分是使用一些開源的成熟的產(chǎn)品,如果出現(xiàn)部分產(chǎn)品是不能滿足我們的業(yè)務(wù)需求時雾家,我們是需要根據(jù)自己的業(yè)務(wù)場景去改造或重新設(shè)計的铃彰,但一般不會從頭開始,也就是我們說的不會重復(fù)造輪子。自己實現(xiàn)一個產(chǎn)品時芯咧,一般都是分布式集群部署的牙捉,那就要考慮,是否需要一個地方集中式存儲分布式集群的元數(shù)據(jù)敬飒?

是否需要進(jìn)行Master選舉實現(xiàn)HA架構(gòu)邪铲?是否需要進(jìn)行分布式協(xié)調(diào)通知?如果有這些需求无拗,zookeeper作為一個久經(jīng)考驗的產(chǎn)品带到,是可以考慮直接拿來使用的,這大大提高我們的效率以及提升產(chǎn)品的穩(wěn)定性英染。

基于以上對zookeeper技術(shù)特點的了解揽惹,如果使用zookeeper來實現(xiàn)系統(tǒng)的高可用時被饿,一般需要考慮4個問題,或者理解為4個步驟:

  • 如何設(shè)計znode的path
  • znode的類型如何選擇搪搏?比如是臨時節(jié)點狭握,還是順序節(jié)點?
  • znode中存儲什么數(shù)據(jù)疯溺?如何表達(dá)自己的業(yè)務(wù)含義
  • 如何設(shè)計watch论颅,客戶端需要關(guān)注什么事件,事件發(fā)生后需要如何處理囱嫩?

下面我們通過兩個案例恃疯,看下zookeeper是如何實現(xiàn)主備切換、集群選舉的挠说。

4.1 主備切換

主備切換在我們?nèi)粘S玫降姆植际较到y(tǒng)很常見澡谭,那我們自己如何通過zookeeper的接口來實現(xiàn)呢愿题?

簡單回顧下主備切換架構(gòu):

image.png

主備架構(gòu)的工作流程:

正常階段的話损俭,業(yè)務(wù)數(shù)據(jù)讀寫在主機,數(shù)據(jù)會復(fù)制到備機潘酗。當(dāng)主機故障了杆兵,數(shù)據(jù)沒辦法復(fù)制到備機,原來的備機自動升級為主機仔夺,業(yè)務(wù)請求到新的主機琐脏。原來的主機恢復(fù)后,成為新的備機缸兔,將數(shù)據(jù)從新的主機同步到備機上

4.1.1 實現(xiàn)方式

image.png

(1)設(shè)計Path

由于只有2個角色日裙,因此直接設(shè)置兩個 znode 即可,master和slave惰蜜,樣例:

  • /com/dewu/order/operate/master
  • /com/dewu/order/operate/slave昂拂。

(2)選擇節(jié)點類型

當(dāng) master 節(jié)點掛掉的時候,原來的 slave 升級為 master 節(jié)點抛猖,因此用 ephemeral 類型的 znode格侯。

因為當(dāng)一個節(jié)點成為master時,需要zk創(chuàng)建master節(jié)點财著,一旦這臺主機掛掉了联四,它到zk的連接就斷掉了,這個master節(jié)點會在超時之后撑教,被zk自動刪除朝墩,這樣的話,就知道原來的主機宕機了伟姐,所以選擇使用ephemeral類型的節(jié)點鱼辙。

(3)設(shè)計節(jié)點數(shù)據(jù)

由于 slave 成為 master 后廉嚼,會成為新的復(fù)制源,可能出現(xiàn)數(shù)據(jù)沖突倒戏,因此 slave 成為 master 后怠噪,節(jié)點需要寫入成為 master 的時間,這樣方便修復(fù)沖突數(shù)據(jù)杜跷。還可以寫入slave上最新的事務(wù)id傍念,這里可以根據(jù)自己的業(yè)務(wù)靈活設(shè)計,znode節(jié)點中應(yīng)該寫入什么數(shù)據(jù)葛闷。

(4)設(shè)計 Watch

節(jié)點啟動的時候憋槐,嘗試創(chuàng)建 master znode,創(chuàng)建成功則切換為master淑趾,否則創(chuàng)建 slave znode阳仔,成為 slave,并監(jiān)聽master節(jié)點; 如果 slave 節(jié)點收到 master znode 刪除的事件扣泊,就自己去嘗試創(chuàng)建 master znode近范,創(chuàng)建成功,則自己成為 master延蟹,刪除自己創(chuàng)建的slave znode评矩。

4.1.2 示意代碼

public class Node {

    public static final Node INSTANCE = new Node();
    private volatile String role = "slave";

    private CuratorFramework curatorFramework;

    private Node(){

    }

    public void start(String connectString) throws Exception{
        if(connectString == null || connectString.isEmpty()){
            throw new Exception("connectString is null or empty");
        }

        CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString(connectString).sessionTimeoutMs(5000).connectionTimeoutMs(3000)
            .retryPolicy(new ExponentialBackoffRetry(1000, 3))
            .build();

        String groupNodePath = "/com/dewu/order/operate";
        String masterNodePath = groupNodePath + "/master";
        String slaveNodePath = groupNodePath + "/slave";

        //watch master node
        PathChildrenCache pathChildrenCache = new PathChildrenCache(client, groupNodePath, true);
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
                    String childPath = event.getData().getPath();
                    System.out.println("child removed: " + childPath);
                    //如果是master節(jié)點刪除了,則自己嘗試變成master
                    if(masterNodePath.equals(childPath)){
                        switchMaster(client, masterNodePath, slaveNodePath);
                    }
                } else if(event.getType().equals(PathChildrenCacheEvent.Type.CONNECTION_LOST)) {
                    System.out.println("connection lost, become slave");
                    role = "slave";
                } else if(event.getType().equals(PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED)) {
                    System.out.println("connection connected……");
                    if(!becomeMaster(client, masterNodePath)){
                        becomeSlave(client, slaveNodePath);
                    }
                }
                else{
                    System.out.println("path changed: " + event.getData().getPath());
                }

            }
        });

        client.start();
        pathChildrenCache.start();
    }

    public String getRole(){
        return role;
    }

    private boolean becomeMaster(CuratorFramework client, String masterNodePath){
        //try to become master
        try {
            client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL)
                .forPath(masterNodePath, Longs.toByteArray(System.currentTimeMillis()));

            System.out.println("succeeded to become master");
            role = "master";

            return true;
        } catch (Exception e) {
            System.out.println("failed to become master: " + e.getMessage());
            return false;
        }
    }

    private boolean becomeSlave(CuratorFramework client, String slaveNodePath) throws Exception {
        //try to become slave
        try {
            client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL)
                .forPath(slaveNodePath, Longs.toByteArray(System.currentTimeMillis()));

            System.out.println("succeeded to become slave");
            role = "slave";

            return true;
        } catch (Exception e) {
            System.out.println("failed to become slave: " + e.getMessage());
            throw e;
        }
    }

    private void switchMaster(CuratorFramework client, String masterNodePath, String slaveNodePath){
        if(becomeMaster(client, masterNodePath)){
            try {
                client.delete().forPath(slaveNodePath);
            } catch (Exception e) {
                System.out.println("failed to delete slave node when switch master: " + slaveNodePath);
            }
        }
    }
}

參考【代碼樣例】【2】

4.2 實現(xiàn)集群選舉

集群選舉的方式比較多阱飘,主要是根據(jù)自己的業(yè)務(wù)場景斥杜,考慮選舉時的一些具體算法。

4.2.1 最小節(jié)點獲勝

就是在共同的父節(jié)點下創(chuàng)建znode沥匈,誰的編號最小蔗喂,誰是leader。

image.png

(1)設(shè)計 Path

集群共用父節(jié)點 parent znode高帖,也就是上圖中的operate缰儿,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode。如上圖中棋恼,假如有5個節(jié)點返弹,編號是從node0000000001~node0000000005。

(2)選擇節(jié)點類型

當(dāng) Leader 節(jié)點掛掉的時候爪飘,持有最小編號 znode 的集群節(jié)點成為新的 Leader义起,因此用ephemeral_sequential 類型 znode。使用ephemeral類型的目的是师崎,leader掛掉的時候默终,節(jié)點能自動刪除,使用sequential類型的目的是,讓這些節(jié)點都是有序的齐蔽,我們選擇最小節(jié)點的時候就比較簡單两疚。

(3)設(shè)計節(jié)點數(shù)據(jù)

可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)。

(4)設(shè)計 Watch

  • 節(jié)點啟動或者重連后含滴,在 parent 目錄下創(chuàng)建自己的 ephemeral_sequntial znode;
  • 創(chuàng)建成功后掃描 parent 目錄下所有 znode诱渤,如果自己的 znode 編號是最小的,則成為 Leader谈况,否則 監(jiān)聽 parent整個目錄;
  • 當(dāng) parent 目錄有節(jié)點刪除的時候勺美,首先判斷其是否是 Leader 節(jié)點,然后再看其編號是否正好比自己小1碑韵,如果是則自己成為 Leader赡茸,如果不是繼續(xù) watch。

Curator 的 LeaderLatch祝闻、LeaderSelector 采用這種策略占卧,可以參考【Curator】【3】看下對應(yīng)的代碼 。

4.2.2 搶建唯一節(jié)點

集群共用父節(jié)點 parent znode联喘,也就是operate华蜒,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode。也就是集群中只有一個節(jié)點耸袜,誰創(chuàng)建成功誰就是leader友多。


image.png

(1)設(shè)計 Path

集群所有節(jié)點只有一個 leader znode牲平,其實本質(zhì)上就是一個分布式鎖堤框。

(2)選擇 znode 類型

當(dāng) Leader 節(jié)點掛掉的時候,剩余節(jié)點都來創(chuàng)建 leader znode纵柿,看誰能最終搶到 leader znode蜈抓,因此用ephemeral 類型。

(3)設(shè)計節(jié)點數(shù)據(jù)

可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)昂儒。

(4)設(shè)計 Watch

  • 節(jié)點啟動或者重連后沟使,嘗試創(chuàng)建 leader znode,嘗試失敗則監(jiān)聽 leader znode;
  • 當(dāng)收到 leader znode 被刪除的事件通知后渊跋,再次嘗試創(chuàng)建leader znode腊嗡,嘗試成功則成為leader ,失敗則繼續(xù)監(jiān)聽leader znode拾酝。

4.2.3 法官判決

整體實現(xiàn)比較復(fù)雜的一個方案燕少,通過創(chuàng)建節(jié)點,判斷誰是法官節(jié)點蒿囤。法官節(jié)點可以根據(jù)一定的邏輯算法來判斷誰是leader客们,比如看誰的數(shù)據(jù)最新等等。

image.png

(1)設(shè)計Path

集群共用父節(jié)點 parent znode,集群中的每個節(jié)點在 parent 目錄下創(chuàng)建自己的 znode底挫。

  • parent znode:圖中的 operate恒傻,代表一個集群,選舉結(jié)果寫入到operate節(jié)點建邓,比如寫入的內(nèi)容可以是:leader=server6盈厘。

  • 法官 znode:圖中的橙色 znode,最小的 znode扑庞,持有這個 znode 的節(jié)點負(fù)責(zé)選舉算法/規(guī)則罐氨。

  • 例如:實現(xiàn)Redis 存儲集群的選舉栅隐,各個 slave 節(jié)點可以將自己存儲的數(shù)據(jù)最新的 trxId寫入到 znode租悄,然后法官節(jié)點將 trxID 最大的節(jié)點選為新的 Leader泣棋。

  • 成員 znode:圖中的綠色 znode,每個集群節(jié)點對應(yīng)一個澈吨,在選舉期間將選舉需要的信息寫入到自己的 znode修赞。

  • Leader znode:圖中的紅色 znode柏副,集群里面只有一個割择,由法官選出來锨推。

(2)選擇節(jié)點類型 當(dāng) Leader 節(jié)點掛掉的時候换可,持有最小編號 znode 的集群節(jié)點成為“法官”慨飘,因此用 ephemeral_sequential 類型 znode译荞。

(3)設(shè)計節(jié)點數(shù)據(jù)

可以根據(jù)業(yè)務(wù)需要靈活寫入各種數(shù)據(jù)圈膏,比如寫入當(dāng)前存儲的最新的數(shù)據(jù)對應(yīng)的事務(wù) ID稽坤。

(4)設(shè)計Watch

  • 節(jié)點啟動或者重連后尿褪,在 parent 目錄下創(chuàng)建自己的 ephemeral_sequntial znode,并 監(jiān)聽 parent 目錄;
  • 當(dāng) parent 目錄有節(jié)點刪除的時候摆马,所有節(jié)點更新自己的 znode 里面和選舉相關(guān)的數(shù)據(jù);
  • “法官”節(jié)點讀取所有 znode的數(shù)據(jù)今膊,根據(jù)規(guī)則或者算法選舉新的 Leader伞剑,將選舉結(jié)果寫入parent znode;
  • 所有節(jié)點監(jiān)聽 parent znode黎泣,收到變更通知的時候讀取 parent znode 的數(shù)據(jù),判斷自己是否成為 Leader坷澡。

4.2.4 集群選舉模式對比

image.png

這里簡單說下計算集群和存儲集群的應(yīng)用場景:

計算集群

無狀態(tài)项郊,不存在存儲集群里所有的數(shù)據(jù)覆蓋問題着降,計算集群只要選出一個leader或主節(jié)點就行蓄喇,對業(yè)務(wù)沒什么影響妆偏。

存儲集群

需要考慮比較復(fù)雜的選舉邏輯楼眷,考慮數(shù)據(jù)一致性罐柳,考慮數(shù)據(jù)盡量不要丟失不要沖突等等,所以需要一個復(fù)雜的選舉邏輯肮蛹。

這里也可以看到伦忠,并不是功能越強大越好,實際上需要考慮不同的應(yīng)用場景赋咽,特性脓匿,基于不同的業(yè)務(wù)要求選擇合適的方案陪毡。一切脫離業(yè)務(wù)場景的方案都是耍流氓爱咬。

5. Etcd

這里再跟大家簡答提一下etcd精拟,主要是zookeeper與etcd的應(yīng)用場景類似蜂绎,在實際的落地選型時也會拿來對比笋鄙,如何選擇。

5.1 etcd概述

除了zookeeper践美,etcd也是最近幾年比較火的一個分布式協(xié)調(diào)框架。
etcd是一種強一致性的分布式鍵值存儲,它提供了一種可靠的方式來存儲需要由分布式系統(tǒng)或機器集群訪問的數(shù)據(jù)杂曲。它優(yōu)雅地處理網(wǎng)絡(luò)分區(qū)期間的leader選舉擎勘,并且可以容忍機器故障。etcd是go寫的一個分布式協(xié)調(diào)組件颖榜,尤其是在云原生的技術(shù)領(lǐng)域里棚饵,目前已經(jīng)成為了云原生和分布式系統(tǒng)的存儲基石。下圖是etcd的請求示意圖:

image.png

通常朱转,一個用戶的請求發(fā)送過來蟹地,會經(jīng)由 HTTP Server 轉(zhuǎn)發(fā)給 邏輯層進(jìn)行具體的事務(wù)處理,如果涉及到節(jié)點的修改藤为,則交給 Raft 模塊進(jìn)行狀態(tài)的變更、日志的記錄夺刑,然后再同步給別的 etcd 節(jié)點以確認(rèn)數(shù)據(jù)提交耘斩,最后進(jìn)行數(shù)據(jù)的提交荚虚,再次同步。

5.2 應(yīng)用場景

與zookeeper一樣,有類似的應(yīng)用場景,包括:

  • 服務(wù)發(fā)現(xiàn)
  • 配置管理
  • 分布式協(xié)調(diào)
  • Master選舉
  • 分布式鎖
  • 負(fù)載均衡

比如openstack 使用etcd做配置管理和分布式鎖漏隐,ROOK使用etcd研發(fā)編排引擎脖隶。

5.3 簡單對比

image.png

在實際的業(yè)務(wù)場景中具體選擇哪個產(chǎn)品庄敛,要考慮自己的業(yè)務(wù)場景怖亭,考慮具體的特性峭跳,開發(fā)語言等等脊岳。目前zookeeper 是用 java 語言的账嚎,被 Apache 很多項目采用旁振,etcd 是用 go 開發(fā)的丛塌,在云原生的領(lǐng)域使用比較多。不過從實現(xiàn)上看彤敛,etcd提供了更穩(wěn)定的高負(fù)載穩(wěn)定讀寫能力袄秩。

結(jié)尾

綜上所述之剧,zookeeper是一個比較成熟的背稼,經(jīng)過市場驗證的分布式協(xié)調(diào)框架,可以協(xié)助我們快速地解決分布式系統(tǒng)中遇到的一些難題。另從上面的介紹中發(fā)現(xiàn)熄阻,zookeeper的核心是zab,etcd的核心是raft袖扛,那可以思考下砸泛,還有哪些一致性算法?在分布式存儲的架構(gòu)里中又有哪些關(guān)聯(lián)呢呢蛆封?這篇文章不做詳細(xì)介紹了唇礁,后面會針對這塊做個詳細(xì)講解。

附錄

【1】Zookeeper官網(wǎng)文檔
https://zookeeper.apache.org/doc/r3.7.0/zookeeperOver.html#zkPerfRW

【2】代碼樣例

https://github.com/yunhua-lee/zk-demo

【3】Curator

https://github.com/apache/curator

【4】【Consensus in the Cloud: Paxos Systems Demystified】

*文/胡強忠

關(guān)注得物技術(shù)惨篱,每周一三五晚18:30更新技術(shù)干貨
要是覺得文章對你有幫助的話盏筐,歡迎評論轉(zhuǎn)發(fā)點贊~

限時活動:

即日起至6月30日,公開轉(zhuǎn)發(fā)得物技術(shù)任意一篇文章到朋友圈砸讳,就可以在「得物技術(shù)」公眾號后臺回復(fù)「得物」琢融,參與得物文化衫抽獎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簿寂,一起剝皮案震驚了整個濱河市漾抬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌常遂,老刑警劉巖纳令,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡平绩,警方通過查閱死者的電腦和手機圈匆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捏雌,“玉大人跃赚,你說我怎么就攤上這事「购觯” “怎么了来累?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵砚作,是天一觀的道長窘奏。 經(jīng)常有香客問我,道長葫录,這世上最難降的妖魔是什么着裹? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮米同,結(jié)果婚禮上骇扇,老公的妹妹穿的比我還像新娘。我一直安慰自己面粮,他們只是感情好少孝,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著熬苍,像睡著了一般稍走。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柴底,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天婿脸,我揣著相機與錄音,去河邊找鬼柄驻。 笑死狐树,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸿脓。 我是一名探鬼主播抑钟,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼野哭!你這毒婦竟也來了在塔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤虐拓,失蹤者是張志新(化名)和其女友劉穎心俗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡城榛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年揪利,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狠持。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡疟位,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喘垂,到底是詐尸還是另有隱情甜刻,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布正勒,位于F島的核電站得院,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏章贞。R本人自食惡果不足惜祥绞,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸭限。 院中可真熱鬧蜕径,春花似錦、人聲如沸败京。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侵贵。三九已至娩怎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隧甚,已是汗流浹背车荔。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戚扳,地道東北人忧便。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像帽借,于是被迫代替她去往敵國和親珠增。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容