1. 程式人生 > >10分鐘看懂: zookeeper 分散式ID (一)

10分鐘看懂: zookeeper 分散式ID (一)

瘋狂創客圈 Java 分散式聊天室【 億級流量】實戰系列之 -25【 部落格園 總入口


文章目錄

寫在前面

​ 大家好,我是作者尼恩。目前和幾個小夥伴一起,組織了一個高併發的實戰社群【瘋狂創客圈】。正在開始高併發、億級流程的 IM 聊天程式 學習和實戰

​ 前面,已經完成一個高效能的 Java 聊天程式的四件大事:

接下來,需要進入到分散式開發的環節了。 分散式的中介軟體,瘋狂創客圈的小夥伴們,一致的選擇了zookeeper,不僅僅是由於其在大資料領域,太有名了。更重要的是,很多的著名框架,都使用了zk。

​ **本篇介紹 ZK 的分散式命名服務 ** 中的 分散式ID生成器。

1.1. ZK 的分散式命名服務

zookeeper的命名服務,主要是利用zookeepeer節點的樹型分層結構和子節點的次序維護能力,為分散式系統中的資源命名與標識能力。

zookeeper的分散式命名服務,典型的應用場景有:

(1)提供分散式JNDI的API目錄服務功能。

可以把系統中各種API介面服務的名稱、連結地址放在zookeeper的樹形分層結果中,提供分散式的API呼叫能力。著名的分散式框架,就是應用了zookeeper的分散式的JNDI能力。

開源的分散式服務框架Dubbo中使用ZooKeeper來作為其命名服務,維護全域性的服務介面API地址列表。在Dubbo實現中,provider服務提供者在啟動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers資料夾下寫入自己的API地址,這個操作就相當於服務的公開。

consumer服務消費者啟動的時候,訂閱節點/dubbo/{serviceName}/providers資料夾下的provider服務提供者URL地址,獲得所有的訪問提供者的API。

(2)製作分散式的ID生成器,為分散式系統中的每一個數據資源,提供的唯一的標識能力。

在單體服務環境下,我們唯一標識一個數據資源,通常利用資料庫的主鍵自增功能。但是在大量伺服器叢集的場景下,依賴單體服務的資料庫主鍵自增生成唯一ID,沒有辦法滿足高併發和高負載的需求。

(3)分散式節點的命名服務

一個分散式系統會有很多的節點組成,而且,節點的數量是不斷動態變化的。根據業務的膨脹需要和迎接流量洪峰,可能會加入大量的動態很多節點。流量洪峰過去,就需要下線大量的節點。或者說,由於機器或者網路的原因,一些節點主動的離開的叢集。

如何為大量的動態節點命名呢?一種簡單的辦法是,可以通過配置檔案,手動的進行每一個節點的命名。但是如果節點資料量太大,或者說變動頻繁,手動命名是不現實的,這就需要用到分散式節點的命名服務。

瘋狂創客圈的分散式IM實戰專案,也會使用分散式命名服務,為每一個IM節點動態命名。

1.1.1. 分散式 ID 生成器的型別

在分散式系統中,ID生成器的使用場景,非常非常多:

(1)大量的資料記錄,需要分散式ID

(2)大量的系統訊息,需要分散式ID

(3)大量的請求日誌,如http請求記錄,需要唯一標識,以便進行後續的使用者行為分析和呼叫鏈路分析,等等等等。

傳統的資料庫自增主鍵,或者單體的自增主鍵,已經不能滿足需求。在分散式系統環境中,迫切需要一個全新的唯一ID的系統,這個系統需要滿足以下需求:

(1)全域性唯一:不能出現重複ID

(2)高可用:ID生成系統是基礎系統,被許多關鍵系統呼叫,一旦宕機,會造成嚴重影響。

分散式唯一ID生成分案有很多種:

(1) java的UUID

(2) 利用分散式快取Redis生成ID

利用Redis的原子操作INCR和INCRBY,生成全域性唯一的ID。

(3) Twitter的snowflake演算法

(4) ZooKeeper生成ID

利用ZooKeeper 的順序節點,生成全域性唯一的ID。

(5) MongoDb的ObjectId

利用分散式Nosql MongDB,生成全域性唯一的ID。

首先分析一下java語言中的 UUID方案。

UUID方案

UUID是Universally Unique Identifier的縮寫,它是在一定的範圍內(從特定的名字空間到全球)唯一的機器生成的識別符號。UUID在其他語言中也叫GUID,在java中,生成UUID的程式碼很簡單:

String uuid = UUID.randomUUID().toString()

一個UUID是16位元組長的數字,一共128位。通常以36位元組的字串表示,比如:3F2504E0-4F89-11D3-9A0C-0305E82C3301。 使用的時候,可以把中間的4箇中劃線去掉,剩下32位字串。

UUID經由一定的演算法機器生成,為了保證UUID的唯一性,規範定義了包括網絡卡MAC地址、時間戳、名字空間(Namespace)、隨機或偽隨機數、時序等元素,以及從這些元素生成UUID的演算法。UUID的只能由計算機生成。

UUID的優點:本地生成ID,不需要進行遠端呼叫,時延低,效能高。

UUID的缺點:UUID過長,16位元組128位,通常以36長度的字串表示,很多場景不適用,比如,由於UUID沒有排序,無法保證趨勢遞增,用做資料庫索引欄位的效率就很低,新增記錄儲存入庫時效能差

從高併發,高可用的角度出發,通過ZooKeeper實現分散式系統唯一ID的方案,是最為合適的解決方案之一。

1.1.2. ZK生成分散式ID

通過建立ZK的順序模式的節點,可以生成全域性唯一的ID。

程式碼如下:


    private String createSeqNode(String pathPefix) {
      try {
            // 建立一個 ZNode 順序節點
            String destPath = client.create()
                    .creatingParentsIfNeeded()
                    .withMode(CreateMode.*EPHEMERAL_SEQUENTIAL*)
//避免zookeeper的順序節點暴增,可以刪除建立的順序節點
                    .forPath(pathPefix);
            return destPath;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

節點建立完成後,會返回節點的完整的層次路徑,生成的序號,放置在路徑的末尾。一般為10位數字字元。

通過擷取路徑末尾的數字,作為新生成的ID。擷取數字的程式碼如下:

public String makeId(String nodeName) {
    String str = createSeqNode(nodeName);
    if (null == str) {
        return null;
    }
    int index = str.lastIndexOf(nodeName);
    if (index >= 0) {
        index += nodeName.length();
        return index <= str.length() ? str.substring(index) : "";
    }
    return str;
}

呼叫的程式碼如下:

*/\*** ** create by 尼恩 @ 瘋狂創客圈* **\*/*@Slf4j
public class IDMakerTester {
    @Test
    public void testMakeId() {
        IDMaker idMaker = new IDMaker();
        idMaker.init();
        String nodeName = "/test/IDMaker/ID-";
        for (int i = 0; i < 10; i++) {
            String id = idMaker.makeId(nodeName);
            log.info("第"+ i + "個建立的id為:" + id);
        }
        idMaker.destroy();
    }
}

下面是部分的執行輸出:


第0個建立的id為:0000000010

第1個建立的id為:0000000011

寫在最後

​ 下一篇:基於 zookeeper 實現snowflake 演算法 。


瘋狂創客圈 億級流量 高併發IM 實戰 系列

  • Java (Netty) 聊天程式【 億級流量】實戰 開源專案實戰