zookeeper 的設計原理

zookeeper 的設計原理

首先介紹下大數據相關的知識。

大數據技術體系

zookeeper 的設計原理

大數據必備技能

  • 編程語言:Java/Python/Scala

  • HDFS原理、MapReduce原理及編程、YARN原理、Hadoop集群搭建

  • Hive原理、HQL、自定義函數、數據倉庫設計

  • Spark原理、SparkStreaming編程、SparkSQL

  • Kafka原理、配置搭建、JavaAPI

  • Flume原理、搭建

  • Zookeeper原理、搭建

大數據就業方向

zookeeper 的設計原理

zookeeper 的設計原理

介紹完大數據的一些利好後,我們來看我們邁出的第一步:zookeeper。

zookeeper 概述

什麼是zookeeper?

a service for coordinating(協調) processes of distributed applications,是一個重要的基礎服務,目標是從更底層提供一個簡單、高性能的服務,用來按需構建同步服務


zookeeper(動物管理員),為什麼叫這個名字?

zookeeper是Hadoop和Hbase的重要組件,hadoop裡面各種組件都是以動物命名的,而zookeeper相當於這動物園的管理員了


zookeeper特點是什麼?

提供了一組通用(generic)的,無等待的(wait-free)api,同時提供了兩個重要的特性:

  • 保證每個客戶端請求FIFO

  • 每個客戶端請求FIFO,所有事務請求線性有序

  • 事務請求指能改變狀態的寫請求


zookeeper 介紹

我們先來回答為什麼需要 zookeeper?

在傳統的應用程序中,線程、進程的同步,都可以通過操作系統提供的機制來完成。但是在分佈式系統中,多個進程之間的同步,操作系統層面就無能為力了。這時候就需要像ZooKeeper這樣的分佈式的協調(Coordination)服務來協助完成同步。

分佈式系統中對於 Coordination 提出了各種各樣的需求:

  • Configuration:包括靜態的操作參數和動態的配置參數

  • Group membership:維護組中存活server的信息

  • leader election:維護每個server都負責什麼

解決上述coordination需求的一種方案是:為每種coordination需求都開發專門的服務。但是我們要知道一個道理:更powerful primitives的實現可以用於less powerful primitives,所以基於這個假設我們在設計coordination的服務上:我們不在實現具體的primitives,而是提供通用(generic)的API來實現滿足個性化的primitives,一旦作出這種決策,帶來的好處有兩點:

  • coordination kernel幫助我們在不改變服務核心的情況下實現新的primitives

  • 根據應用需求提供更多樣化的primitives

在設計ZooKeeper的API的時候,我們移除了阻塞primitives,例如鎖,基於的考慮有如下兩點:

  • 阻塞primitives會導致處理慢的客戶端影響相對較快的客戶端

  • 由於請求在處理上依賴於其他客戶端的響應和失敗的檢查,那ZooKeeper本身實現上也會更復雜【一個客戶端鎖了,必須等待他釋放鎖。或者由於掉線強制釋放鎖】

ZooKeeper由於實現了wait-free的數據對象,從而和其他基於阻塞語義(blocking primitives)有了顯著的區別,ZooKeeper在組織wait-free的數據對象借鑑了文件系統的思路,將wait-free的數據對象按層級組織起來,不同只是移除了open和close這種阻塞方法。

ZooKeeper實現了pipelined architecture,提高了系統的吞吐。客戶端可以同時發出多個請求,異步執行,同時保證請求的FIFO。

為了實現寫請求linearizable,實現了Zab協議,一個leader-based atomic broadcast protocol,但是對於讀請求,我們不適用Zab,只是本地讀,這樣能很方便的擴展系統。

在客戶端緩存數據可以有效的提高系統性能,但是緩存的數據怎麼更新呢?ZooKeeper使用watch機制,不直接操作客戶端緩存,這是因為:由於Chubby直接管理客戶端緩存,一旦某個客戶端處理慢了(可能是掛了),會導致阻塞數據更新。針對這個問題,Chubby 使用租期來解決,一旦某個客戶端有錯誤,不會影響更新操作太長時間,但這也只是確定了影響的上限,無法避免,而ZooKeeper的watches可以徹底解決改問題。

注:Chubby 是什麼?

Google的三篇論文影響了很多很多人,也影響了很多很多系統。這三篇論文一直是分佈式領域傳閱的經典。根據MapReduce,於是我們有了Hadoop;根據GFS,於是我們有了HDFS;根據BigTable,於是我們有了HBase。而在這三篇論文裡都提及Google的一個lock service—Chubby,於是我們有了Zookeeper。

總結起來,本文的主要內容是:

  • Coordination kernel:提出了wait-free的 Coordination service,能夠保證 relaxed consistency,為其他同步技術提供基本原語。

  • Coordination recipes:通過 ZooKeeper 可以實現high level的同步原語,包括了強同步和強一致的同步(zookeeper本身提供的是wait-free的同步原語)

  • Experience with Coordination:心得,具體案例和評測

Zookeeper服務

ZooKeeper提供了client library來訪問服務,client library主要做兩件事:

  • 管理client和ZooKeeper之間的網絡連接

  • 提供ZooKeeper的api

術語:

  • client:a user of the ZooKeeper service

  • server:a process providing the ZooKeeper service

  • znode:an in-memory data node in the ZooKeeper data

  • data tree:像文件系統一樣按層級組織的命名空間

  • update,write:改變data tree狀態的操作

  • session:client和ZooKeeper之間的網絡連接

Service overview

zookeeper 的設計原理

ZooKeeper給客戶端提供了znode的抽象,客戶端通過api來操作znode中存儲的數據,znode的地址類似文件系統中的path,像上圖中節點p_1就通過路徑/app1/p_1來訪問,客戶端可以創建兩種znode:

  • Regular: 需要客戶端顯式的創建和刪除

  • ephemeral: 客戶端創建,也可以刪除,也可以當會話終止時候讓系統自動刪除

除此之外,創建的時候可以帶sequential的flag,此時創建znode p,則會自動帶上一個下標n,n是一個單調遞增的數,並且滿足seq(parent)>= max(children),意思是新建的node,其下標總是大於其父節點下面創建過的所有node的最大n。


watches怎麼創建?

讀請求上設置watch參數


watches作用?

客戶端不必輪詢服務器獲取數據,當數據發生改變的時候,通知客戶端


watches什麼時候失效?

當數據發生改變通知客戶端後

session關閉


watches通知了什麼?

watches通知只是告知狀態改變了,但是不提供改變的數據


數據模型

如圖一所示:類似於文件系統,但是znodes不是用來做數據存儲的,而是用來跟實際的應用映射的,像圖1中,有兩個應用app1,app2,app1下面實現了個簡單的group membership protocol。

雖然znode設計之初不是為了存儲數據,但是也可以存儲一些meta-data或者configuration信息,同時znode本身會存儲time stamps 和 version counters等元信息

會話(sessions)

代表client和ZooKeeper之間的網絡連接,作用有:

  • server端可以通過sessions超時來判斷客戶端是否健在

  • 客戶端可以通過sessions觀察其操作的一連串變化

  • sessions使得client的連接可以從一個server透明的轉移到另一個server,因此可以持續的提供client服務

Client API

zookeeper 的設計原理

以上所有操作有syn和asyn兩個版本。ZooKeeper的客戶端保證所有寫操作是完全有序的,寫操作後其他client的寫能看到。

在訪問的znode的時候都是通過完整的path來訪問的,而不是像文件系統那樣通過open,close來操作文件句柄,大大簡化了servers端的複雜度,不需要保存額外的信息了。

ZooKeeper guarantees

  • Linearizable writes:所有寫請求有序

  • FIFO client order:每個客戶端請求FIFO

考慮場景:leader election

當新的leader產生的時候,需要改變大量的配置後,通知其他processes,需要滿足兩個要求:

  • 新leader改變配置的時候,其他processes不能讀取不完整的配置

  • 新leader在改變配置過程中掛了,其他processes不能使用這個不完整的配置

通過鎖能滿足第一個需求,zookeeper的實現:

  1. 新leader改變前刪除 ready znode

  2. 改變配置(通過pipeline加速)

  3. 新建 ready znode

因為寫順序的保證,其他客戶端能看到ready的時候,肯定新配置也生效了,如果在更改配置中leader掛了,就不會有ready。

上面仍然有一個問題:如果process先是看到了ready,此時在讀取之前,leader刪除了ready,開始更改配置,那process會讀取到不完整的配置了,怎麼解決呢?

這是通過對通知的順序性保證解決的,具體來說就是:如果客戶端在watch一個Ready改變事件,那只有當配置改變後,才會通知client Ready有變化的事件(不是Ready刪除就通知事件),這就保證了客戶端收到通知,肯定是配置變化了。

另一個可能的問題是:客戶端之間除了ZooKeeper之外,還有別的通信通道,場景是:

A和B在ZooKeeper上有共享數據,A改變數據後,通過其他通信手段告訴B數據改變了,此時B去讀取數據,可能會讀取不到改變的數據,因為ZooKeeper集群可能存在的主從延遲,解決方案是:B讀之前先發個sync請求,類似於文件系統中的flush操作,讓pending的寫請求真正執行。

除此之外,ZooKeeper還有兩個保證:

  • 高可用,只要大多數機器還存活,就能提供服務

  • 數據可靠:只要ZooKeeper回覆寫成功,則數據最終一定會存在在服務器上

ZooKeeper 實現

作為一個 coordination,非常重要的就是高可用和數據可靠性,我們來看下如何實現的。

先來看數據的寫入過程:

zookeeper 的設計原理

  1. 客戶端提交寫請求

  2. follower寫請求交給leader,由leader作為整個事務的協調者,負責整個寫入過程

  3. leader在整個事務中是通過ZAB算法保證了數據的最終一致,由leader發起事務提議(重點是一個zxid,全局遞增的一個id生成器,通過zxid來達到全局時鐘的效果)

  4. follower接收到leader發起的事務提議,返回收到(所有請求都是在一個FIFO的隊列中)

  5. leader在收到follower的回覆後,提交本次事務

  6. 客戶端收到回覆

此處ZAB算法是保證數據一致性的關鍵,我們在raft那再講。

總結

本文主要是介紹了zookeeper是什麼:一個開源的針對大型分佈式系統的可靠協調系統;設計目標是:將複雜且容易出錯的分佈式式一致性服務封裝起來,構成一個高效可靠的原語集,並以簡單易用的接口提供給用戶使用,其特性有:

  • 最終一致性

  • 順序性:從同一個客戶端發起的事務請求,最終會嚴格地按照其發送順序被應用到Zookeeper中。

  • 可靠性:一旦服務器成功的應用一個事務,並完成了客戶端的響應,那麼該事務所引起的服務端狀態變更將會被一直保留下去。

  • 實時性:Zookeeper不能保證兩個客戶端能同時得到剛更新的數據,如果需要最新數據,應該在讀數據之前調用sync()接口。

  • 原子性:一次數據更新要麼成功,要麼失敗。

  • 單一視圖:無論客戶端連接到哪個服務器,看到的數據模型都是一致的。

其中一致性算法將會在raft中講解。

你的鼓勵是我繼續寫下去的動力,期待我們共同進步。


分享到:


相關文章: