ZooKeeper 分布式過程協(xié)同技術詳解

ZooKeeper 分布式過程協(xié)同技術詳解

ZooKeeper 分布式過程協(xié)同技術詳解
image.png
image.png

簡介

分布式系統(tǒng)

  • 分布式系統(tǒng)是同時跨越多個物理主機, 獨立運行的多個軟件所組成的系統(tǒng)锻梳。

  • 分布式系統(tǒng)中的兩種進程通信方式

    • 方式一: 直接通過網絡進行信息交換
    • 方式二: 讀寫某些共享存儲
    分布式系統(tǒng)中的兩種進程通信方式
  • 分布式系統(tǒng)需注意以下幾點

    • 消息延遲: (如網絡擁堵)箭券,導致消息不是按順序到達
    • 處理器性能: 導致消息延遲處理
    • 時鐘偏移: 導致系統(tǒng)作出錯誤決策

Zookeeper使用場景

  • 適用場景
    • 選舉集群主節(jié)點
    • 檢測崩潰
    • 存儲集群元數(shù)據(jù)
    • 服務發(fā)現(xiàn)
    • 數(shù)據(jù)分片
  • 不適用場景
    • 海量數(shù)據(jù)存儲

主-從應用

主-從示例

主節(jié)點失效

  • 備份主節(jié)點: 在主節(jié)點崩潰時接管主節(jié)點的角色, 進行故障轉移,并恢復到舊主節(jié)點崩潰時的狀態(tài)
  • 腦裂: 誤判主節(jié)點崩潰(如消息延遲), 而導致集群中出現(xiàn)兩個主節(jié)點

從節(jié)點失效

從節(jié)點失效

通信故障

  • 某臺機器網絡連接斷開, 導致斷開的機器和網絡中的某一從節(jié)點執(zhí)行同一任務

主-從應用的需求

綜上所述, 我們對主-從應用的需求如下:

  • 主節(jié)點選舉
  • 崩潰檢測
  • 組成員關系管理
  • 元數(shù)據(jù)管理

了解Zookeeper

Zookeeper基礎

Zookeeper數(shù)據(jù)樹結構示例

節(jié)點稱為znode節(jié)點

Zookeeper數(shù)據(jù)樹結構示例

API

znode節(jié)點是數(shù)據(jù)操作的最小單元, Zookeeper不允許局部寫入或讀取zonde節(jié)點的數(shù)據(jù)

API 說明
create /path data 創(chuàng)建一個名為/path的znode節(jié)點,并包含數(shù)據(jù)data
create -e /master "master1.example.com:2223"(-e表示臨時節(jié)點)
create -s /tasks/task- "cmd"(-s創(chuàng)建有序節(jié)點)
delete /path 刪除名為/path的zonde
exists /path 檢查是否存在名為/path的節(jié)點
set /path data 設置名為/path的zonde的數(shù)據(jù)為data
get /path 返回名為/path節(jié)點的數(shù)據(jù)信息
getChildren /path 返回/path節(jié)點的所有子節(jié)點列表
ls /path 查看/path下的節(jié)點, 例如ls /查看根節(jié)點下有哪些節(jié)點

znode的不同類型

新建znode時, 還需要指定該節(jié)點的類型(mode),不同的類型決定了znode節(jié)點的行為方式

節(jié)點類型 重點說明 刪除時機 案例
持久節(jié)點(persistent) 只能通過調用delete刪除 保存<font color=red>從</font>節(jié)點的任務分配情況,即使分配任務的<font color=red>主</font>節(jié)點節(jié)點崩潰了
臨時節(jié)點(ephemeral) <font color=red>Zookeeper不允許臨時節(jié)點有子節(jié)點(因為臨時節(jié)點會自動刪除)</font> 1.創(chuàng)建該節(jié)點的客戶端崩潰或斷開連接
2.主動調用delete刪除
檢測主/從節(jié)點崩潰時,需要每個節(jié)點建立臨時節(jié)點
有序節(jié)點(sequential) 創(chuàng)建時,一個序號會被追加到路徑后,例如創(chuàng)建路徑為task/task-,則最終路徑為task/task-1 名稱唯一,可直觀查看znode的創(chuàng)建順序

上面3中類型排列組合可知, znode節(jié)點有4種類型:

  1. 持久節(jié)點
  2. 臨時節(jié)點
  3. 持久有序節(jié)點
  4. 臨時有序節(jié)點

監(jiān)視與通知

一種常見的輪詢問題
一種常見的輪詢問題
基于通知機制
Zookeeper基于通知機制
監(jiān)視點

通知機制是單次出發(fā)的操作, 即設置一次監(jiān)視點, 只會發(fā)出一次變動通知。通過每次設置監(jiān)視點前讀取Zookeeper的狀態(tài)來防止錯過任何變更疑枯。

Zookeeper每次設置監(jiān)視點前都查看zookeeper狀態(tài)辩块,來防止錯過變更
使用版本控制
Zookeeper使用版本來阻止并行操作的不一致

Zookeeper架構

架構總覽

Zookeeper服務器運行在兩種模式下:

模式 說明
獨立模式(standalone) 即單機模式
仲裁模式(quorum) 即一組集群
Zookeeper使用版本來阻止并行操作的不一致

仲裁

仲裁模式下,Zookeeper復制集群種所有服務器的數(shù)據(jù)樹荆永。但如果讓一個客戶端等所有服務器完成數(shù)據(jù)保存后再繼續(xù)废亭,便會導致無法接受的延遲問題。Zookeeper的解決方案是使用法定人數(shù)屁魏,即只要法定個數(shù)的服務器完成數(shù)據(jù)保存后滔以,客戶端即可繼續(xù)。

會話(Session)

  • 當客戶端通過某一個特定語言套件來創(chuàng)建一個Zookeeper句柄時氓拼,它就會通過服務建立一個會話(Session)你画。然后通過TCP協(xié)議與服務器通信
  • 當會話無法與當前連接的服務器繼續(xù)通信時,Zookeeper客戶端就可能透明地將會話轉移到另一個服務器上
  • 同一個會話提供了順序保障桃漾,即請求會先進先出

開始使用Zookeeper

安裝

  1. 下載Zookeeper安裝包并解壓. tar -xvzf zookeeper-3.4.8.tar.gz
  2. 創(chuàng)建配置文件(conf目錄下有樣例配置)坏匪。 mv conf/zoo_sample.cfg conf/zoo.cfg.
  3. 最好將data移除/tmp目錄, 防止Zookeeper填滿根分區(qū), 修改zoo.cfg中的配置dataDir=/tmp/zookeeper
  4. 啟動Zookeeper服務器. bin/zkServer.sh start (bin/zkServer.sh start-foreground 可在屏幕上看到服務器日志輸出)
  5. 啟動Zookeeper客戶端. bin/zkCli.sh
    日志
  6. 命令使用方式


生命周期和會話

生命周期
  • 客戶端與服務器因網絡超時斷開連接后, 客戶端仍然保持connecting狀態(tài)。因為對聲明會話超時負責的是服務端撬统,而不是客戶端适滓。
  • 會話超時設置為時間t
時間 行為
服務端 t 經過時間t后,服務端接收不到這個會話的任何消息恋追,服務端會聲明會話過期
客戶端 t/3 經過t/3時間未收到服務端消息凭迹,則主動向服務器發(fā)送心跳消息
客戶端 2t/3 仍未收到服務端消息,則開始尋找其它的服務器,此時它還有t/3的時間去尋找
  • 仲裁模式(集群模式)下苦囱,Zookeeper需要傳遞可用的服務器列表給客戶端嗅绸,告知客戶端可以連接的服務器信息并選擇一個進行連接
  • 斷開連接后,客戶端不能連接到一個比自己狀態(tài)舊的服務器撕彤,Zookeeper處理方式見下圖
客戶端重連情況下事物(zxid)標識符的使用

仲裁模式

我們可以在一臺機器上運行多個Zookeeper鱼鸠,來構建仲裁模式(集群模式)

  • 配置如下
# 在數(shù)據(jù)文件存儲目錄下建立文件myid,并寫入內容1(zookeeper id),便可定義該zookeeper服務器ID。 命令: echo 1>/tmp/z1/data/myid
# 這樣做的好處是, 部署到不同機器的zookeeper不需要改配置文件
dataDir=/tmp/z1/data
# 客戶端連接的端口號
clientPort=2182
# /root/dev/research/zookeeper-3.4.8/conf/zoo.cfg
# server.{n即zookeeper服務器id}=[ip地址或hostname]:[集群通信端口號]:[群首選舉端口號]
server.1=127.0.0.1:2222:2223
server.2=127.0.0.1:3333:3334
server.3=127.0.0.1:4444:4445
  • 實踐(Zookeeper部署示意圖)

    Zookeeper部署示意圖
    • 最初只啟動z3羹铅,從日志可以看出z3會瘋狂嘗試連接到其它服務器蚀狰,然后失敗。
    Zookeeper部署示意圖
    • 然后再啟動z2职员,此時達到了法定人數(shù)麻蹋。從日志可以看出,z3被選為了leader廉邑,z2作為follower被激活

      Zookeeper-3日志
      Zookeeper-2日志
  • 使用zkCli.sh訪問集群./zkCli.sh -server 127.0.0.1:2182,127.0.0.1:2181(如果希望客戶端只連接上海機房集群,可以只輸入上海zookeeper服務器ip)

    隨機連接集群中一臺機器

    zookeeper可以實現(xiàn)簡單的負載均衡哥蔚,但是無法按權重來進行負載倒谷。

  • 現(xiàn)在我們來實現(xiàn)一個原語:通過zookeeper實現(xiàn)鎖

    通過zookeeper實現(xiàn)鎖

一個主-從模式例子的實現(xiàn)

主-從模式例子的實現(xiàn)

主節(jié)點角色

從節(jié)點,客戶端(略)糙箍,見上面分析圖

# 連接Zookeeper
[root@iZ11bh64aveZ bin]# ./zkCli.sh -server 127.0.0.1:2182,127.0.0.1:2181

# -e表示臨時節(jié)點
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 0] create -e /master "master1.example.com:2223"
Created /master
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 1] ls /
[zookeeper, master]
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 2] get /master
master1.example.com:2223
cZxid = 0x10000000c
ctime = Wed Feb 22 23:05:34 CST 2017
mZxid = 0x10000000c
mtime = Wed Feb 22 23:05:34 CST 2017
pZxid = 0x10000000c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x25a6118b1220004
dataLength = 24
numChildren = 0

# /master已經存在渤愁,無法重復創(chuàng)建
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 4] create -e /master "master1.example.com:2223"
Node already exists: /master

# 對/master節(jié)點設置監(jiān)視點
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 5] stat /master true
cZxid = 0x10000000c
ctime = Wed Feb 22 23:05:34 CST 2017
mZxid = 0x10000000c
mtime = Wed Feb 22 23:05:34 CST 2017
pZxid = 0x10000000c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x25a6118b1220004
dataLength = 24
numChildren = 0

任務隊列化(有序節(jié)點的應用)

可以使用有序節(jié)點來達到任務隊列化的目的。這樣做有兩個好處:

  1. 序列號指定了任務被隊列化的順序深夯;
  2. 可以通過很少的工作為任務創(chuàng)建基于序列號的唯一路徑抖格。

使用Zookeeper的API

建立Zookeeper會話

句柄

  • Zookeeper的API圍繞句柄(handle)而構建,每個API調用都需要傳遞handle咕晋。它代表與Zookeeper之間的一個會話
句柄 說明
只要會話活著雹拄,句柄就仍然有效 例如會話在斷開后被遷移到另一臺服務器,由于此時會話還有效掌呜,因此句柄仍然有效
如果句柄關閉滓玖,則終止會話 如果句柄關閉,Zookeeper客戶端會告知服務器終止這個會話
- 如果Zookeeper發(fā)現(xiàn)客戶端死掉质蕉,就會使該會話無效势篡。如果客戶端嘗試使用該會話的那個句柄連接,那么服務器會通知客戶端該會話已失效模暗,這個句柄進行的任何操作都會返回錯誤
創(chuàng)建Zookeeper句柄API
  • API
/**
    connectString: 連接zookeeper的主機和端口信息禁悠,如:127.0.0.1:2182,127.0.0.1:2181
    sessionTimeout: 超時時間,單位ms。一般設置為5-10秒兑宇。sessionTimeout=15s表示Zookeeper如果有15s無法和客戶端通信碍侦,則會終止該會話。
    watcher:Watcher對象(需自己實現(xiàn)Watcher接口)隶糕,用于監(jiān)控會話(如建立/失去連接瓷产,Zookeeper數(shù)據(jù)變化,會話過期等事件)
*/
Zookeeper(String connectString,int sessionTimeout, Watcher watcher)
  • 實現(xiàn)一個Watcher
package org.apache.zookeeper.book.luyunfei;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;

/**
 * Created by luyunfei on 24/02/2017.
 */
public class Master implements Watcher {

    @Override
    public void process(WatchedEvent watchedEvent) {
        // 監(jiān)控到連接斷開時,自己不要關閉會話后再啟動一個新的會話枚驻,這樣會增加系統(tǒng)負載拦英,并導致更長時間的中斷, Zookeeper客戶端庫會處理
        System.out.println("監(jiān)控: "+watchedEvent);
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        String connectString = "139.196.53.21:2182,139.196.53.21:2181";
        // 注意:如果服務器發(fā)現(xiàn)請求的會話超時時間太長或太短,服務器會調整會話超時時間
        int sessionTimeout = 10 * 1000;
        Master obj = new Master();
        ZooKeeper zooKeeper = new ZooKeeper(connectString, sessionTimeout, obj);
        Thread.sleep(600000);
        //客戶端可以調用Zookeeper.close()方法主動結束會話. 否則即便客戶端斷開連接测秸,會話也不會立即消失,服務端要等會話超時以后才結束會話
        zooKeeper.close();
    }
}

## 執(zhí)行上述代碼,啟動客戶端
2017-02-24 22:49:29,359 - INFO  - [main:ZooKeeper@438] - Initiating client connection, connectString=139.196.53.21:2182,139.196.53.21:2181 sessionTimeout=10000 watcher=org.apache.zookeeper.book.luyunfei.Master@3e6fa38a
2017-02-24 22:49:29,399 - INFO  - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1032] - Opening socket connection to server 139.196.53.21/139.196.53.21:2182. Will not attempt to authenticate using SASL (unknown error)
2017-02-24 22:49:29,488 - INFO  - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@876] - Socket connection established to 139.196.53.21/139.196.53.21:2182, initiating session
2017-02-24 22:49:29,649 - INFO  - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1299] - Session establishment complete on server 139.196.53.21/139.196.53.21:2182, sessionid = 0x25a7098967b0000, negotiated timeout = 10000
監(jiān)控: WatchedEvent state:SyncConnected type:None path:null

## 此時關閉Zookeeper服務器(/tmp/z3/zookeeper-3.4.8/bin/zkServer.sh stop), 程序監(jiān)控到Disconnected事件
2017-02-24 22:57:31,009 - INFO  - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1158] - Unable to read additional data from server sessionid 0x25a7098967b0000, likely server has closed socket, closing socket connection and attempting reconnect
監(jiān)控: WatchedEvent state:Disconnected type:None path:null
....
....
## 此時又開啟Zookeeper服務器(/tmp/z3/zookeeper-3.4.8/bin/zkServer.sh start), 客戶端自動重新連接服務
2017-02-24 22:59:02,559 - INFO  - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1299] - Session establishment complete on server 139.196.53.21/139.196.53.21:2182, sessionid = 0x25a7098967b0000, negotiated timeout = 10000
監(jiān)控: WatchedEvent state:SyncConnected type:None path:null
  • 當看到Disconnected事件時灾常,千萬不要自己創(chuàng)建一個新的Zookeeper句柄來重新連接服務霎冯,Zookeeper客戶端庫會負責在通信恢復時為你重新連接服務。
  • 假設有3臺Zookeeper服務(法定人數(shù)為2),一個服務器故障不會導致服務終端钞瀑∩蜃玻客戶端會很快收到Disconnected事件,之后便為SyncConnected事件
  • 再說一遍: 不要自己試著管理Zookeeper客戶端連接(如監(jiān)控到連接斷開時,自己不要關閉會話后再啟動一個新的會話雕什,這樣會增加系統(tǒng)負載缠俺,并導致更長時間的中斷)显晶。Zookeeper客戶端庫會處理,它會很快重建會話壹士,以便將影響最小化磷雇。

Zookeeper管理接口

Zookeeper有兩種管理接口: JMX和命令,先介紹命令管理接口(stat,dump命令)

# stat命令
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
#(手動敲入stat)
stat
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Clients:
 /127.0.0.1:60538[0](queued=0,recved=1,sent=0)

Latency min/avg/max: 0/0/0
Received: 2
Sent: 1
Connections: 1
Outstanding: 0
Zxid: 0x300000002
Mode: leader
Node count: 4
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#
  • dump命令
# dump命令
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
# (手動敲入dump)
dump
SessionTracker dump:
Session Sets (4):
0 expire at Fri Feb 24 23:27:28 CST 2017:
0 expire at Fri Feb 24 23:27:32 CST 2017:
0 expire at Fri Feb 24 23:27:34 CST 2017:
1 expire at Fri Feb 24 23:27:38 CST 2017:
    0x25a70a1e2b80000
ephemeral nodes dump:
Sessions with Ephemerals (0):
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#

# 此時停止客戶端躏救,過了一段時間才會出現(xiàn)如下信息(即會話過一段時間后才消失唯笙。這是因為直到會話超時時間過了以后,服務端才會結束這個會話)
# 最好在客戶端停止時盒使,會話立即消失崩掘。客戶端可以調用Zookeeper.close()方法主動結束會話
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
dump
SessionTracker dump:
Session Sets (0):
ephemeral nodes dump:
Sessions with Ephemerals (0):
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#

給上面的Master程序增加管理權(創(chuàng)建主節(jié)點)

創(chuàng)建znode節(jié)點

創(chuàng)建Zookeeper節(jié)點API create()----同步調用版本

要重視對異常的處理, 例如: 如果create執(zhí)行創(chuàng)建主節(jié)點成功了少办,此時苞慢,客戶端還不知道自己成功創(chuàng)建了主節(jié)點,然后客戶端死掉了英妓。這個時候就不會再有其它任何進程稱為主節(jié)點進程了挽放。

/**
    String path: 節(jié)點path
    byte[] data: 節(jié)點數(shù)據(jù), 只能傳入字節(jié)數(shù)組類型的數(shù)據(jù). 如果沒有數(shù)據(jù),則可傳new byte[0]
    List<ACL> acl: ACL策略鞋拟。例如
            1. ZooDefs.Ids.OPEN_ACL_UNSAFE為所有人提供了所有權限(該策略在不可信環(huán)境下非常不安全)
            2. 其它策略
    CreateMode: 節(jié)點類型(枚舉骂维,如:臨時節(jié)點,臨時有序節(jié)點贺纲,持久節(jié)點航闺,持久有序節(jié)點)
    拋出兩類異常:
        1. KeeperException
            1.1 ConnectionLossException: KeeperException異常的子類,發(fā)生于客戶度與Zookeeper服務端失去連接時,通常由網絡原因導致
                                        注意:雖然Zookeeper會自己處理重建連接猴誊,但是我們必須知道未決請求的狀態(tài)(是否已經處理/需重新請求)
        2. InterruptedException:發(fā)生于客戶端線程調用了Thread.interrupt, 通常是因為應用程序部分關閉潦刃,但還在其他相關應用的方法中使用
                處理方式:1. 向上直接拋出異常,讓程序最外層捕獲懈叹,然后主動關閉zk句柄(zookeeper.stop()),然后做清理善后
                          2. 如果句柄沒有關閉乖杠,則可能會有其它異步執(zhí)行的后續(xù)操作,這種情況做清理善后會比較棘手
*/
create(String path, byte[] data, List<ACL> acl, CreateMode createMode) throws KeeperException, InterruptedException
創(chuàng)建Zookeeper節(jié)點舉例(創(chuàng)建節(jié)點)
// 創(chuàng)建zonde節(jié)點代碼
    public void runForMaster(ZooKeeper zooKeeper) throws KeeperException, InterruptedException {
        Random random = new Random(this.hashCode());
        String serverId = Integer.toHexString(random.nextInt());
        // 創(chuàng)建節(jié)點
        zooKeeper.create(
                "/master", // 節(jié)點path
                serverId.getBytes(), // 節(jié)點數(shù)據(jù), 這里定義節(jié)點數(shù)據(jù)為代表該客戶端的隨機ID
                ZooDefs.Ids.OPEN_ACL_UNSAFE,// 該節(jié)點的安全策略
                CreateMode.EPHEMERAL// 節(jié)點類型(枚舉澄成,如:臨時節(jié)點胧洒,臨時有序節(jié)點,持久節(jié)點墨状,持久有序節(jié)點)
        );

    }
getData API----同步調用版本
/**
    path: znode節(jié)點路徑
    watch:
        boolean類型版本api: 表示是否想要監(jiān)聽后續(xù)的數(shù)據(jù)變更,true則使用創(chuàng)建句柄時所設置的Watcher對象監(jiān)控事件
        Watcher類型版本api: 表示使用新Watcher對象監(jiān)視變更
    Stat: getData方法可填充節(jié)點的元數(shù)據(jù)信息卫漫,stat表示新填充的元數(shù)據(jù)信息,對象定義如下
        public class Stat implements Record {
            private long czxid;
            private long mzxid;
            private long ctime;
            private long mtime;
            private int version;
            private int cversion;
            private int aversion;
            private long ephemeralOwner;
            private int dataLength;
            private int numChildren;
            private long pzxid;
        }
    返回值: znode節(jié)點數(shù)據(jù)的字節(jié)數(shù)組
*/
byte[] getData(String path, boolean watch, Stat stat)
byte[] getData(String path, Watcher watcher, Stat stat)
// 我們通過Stat結構肾砂,可以獲得當前主節(jié)點創(chuàng)建的時間
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/master", false, stat);
Date startDate = new Date(stat.getCtime());//ctime單位為秒
getData 舉例(申請成為主節(jié)點)
String serverId = Integer.toHexString(new Random(this.hashCode()).nextInt());
    boolean isLeader = false;
    ZooKeeper zooKeeper;

    // 創(chuàng)建zonde節(jié)點(申請成為主節(jié)點)
    public void runForMaster() throws KeeperException, InterruptedException {
        while (true) {
            try {
                // 創(chuàng)建/master節(jié)點列赎,如果執(zhí)行成功,本客戶端將會成為主節(jié)點
                zooKeeper.create(
                        "/master",
                        serverId.getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.EPHEMERAL
                );
            } catch (KeeperException.NodeExistsException e) {
                // 主節(jié)點已經存在了镐确,不再申請成為主節(jié)點
                isLeader = false;
                break;
            } catch (KeeperException.ConnectionLossException e) {
                // 這里留空包吝,以便在網絡異常情況下饼煞,繼續(xù)while循環(huán)來申請成為主節(jié)點
            }
            // 如果已經存在主節(jié)點,則跳出while循環(huán)诗越,不再申請成為主節(jié)點
            if (checkMaster()) break;
        }
    }

    // 檢查是否存在主節(jié)點
    public boolean checkMaster() throws KeeperException, InterruptedException {
        while (true) {
            try {
                Stat stat = new Stat();
                // 通過獲茸┣啤/master節(jié)點數(shù)據(jù),并和自身的serverId比較掺喻,來檢查活動主節(jié)點
                byte[] data = zooKeeper.getData("/master", false, stat);
                isLeader = new String(data).equals(serverId);
                // 已經存在主節(jié)點了芭届,返回true,告知runForMaster()不要再進行while循環(huán)了
                return true;
            } catch (KeeperException.NoNodeException e) {
                return false;
            } catch (KeeperException.ConnectionLossException e) {

            }
        }
    }
getData API----異步調用版本

雖然是異步的, 但是順序能夠得到保證感耙。因為為了保持順序褂乍,只會有一個單獨的線程按照接收順序處理響應包

/**
    StringCallback: 提供回調方法的對象,它通過傳入的上下文參數(shù)(Object ctx)來獲取數(shù)據(jù)即硼。它實現(xiàn)StringCallback接口
    Object ctx: 用戶指定上下文信息逃片,最終會傳入回調函數(shù)中
*/
void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx)

/**
    以下演示了如何創(chuàng)建一個StringCallback回調方法的對象。
    注意: 因為只有一個單獨的線程處理所有回調調用只酥,因此一個回調函數(shù)阻塞褥实,會導致后續(xù)所有回調函數(shù)都阻塞。
            所以要避免在回調函數(shù)里做集中操作(即while(true){...})或阻塞操作(如調用synchronized方法).以便其被快速處理

*/
AsyncCallback.StringCallback cb = new AsyncCallback.StringCallback() {
        /**
            rc : 返回碼裂允,0: 正常返回损离;其它表示對應的KeeperException異常
            path: create()傳入的參數(shù),即znode路徑
            ctx: create()傳入的參數(shù)绝编,即上下文
            name: znode節(jié)點最終的名稱(一般path和那么值一樣僻澎。單如果采用CreateMode.SEQUENTIAL有序模式,則name為最終名稱)
        */
        public void processResult(int rc, String path, Object ctx, String name) {
            switch (Code.get(rc)) {
            case CONNECTIONLOSS:
                checkMaster();

                break;
            case OK:
                state = MasterStates.ELECTED;
                takeLeadership();

                break;
            case NODEEXISTS:
                state = MasterStates.NOTELECTED;
                masterExists();

                break;
            default:
                state = MasterStates.NOTELECTED;
                LOG.error("Something went wrong when running for master.",
                        KeeperException.create(Code.get(rc), path));
            }
            LOG.info("I'm " + (state == MasterStates.ELECTED ? "" : "not ") + "the leader " + serverId);
        }
    };
AsyncCallback.StringCallback cb = new AsyncCallback.StringCallback() {
        public void processResult(int rc, String path, Object ctx, String name) {

        }
    };

getData API----異步調用版本

異步版本比同步版本簡單, 沒有while(true){...}十饥,防止一直卡在本線程導致其它線程無法執(zhí)行

void checkMaster() {
        zk.getData("/master", false, masterCheckCallback, null);
    }

設置元數(shù)據(jù)

這里和create API沒啥不同窟勃,有個技巧見代碼注釋

public void bootstrap(){
        createParent("/workers", new byte[0]);
        createParent("/assign", new byte[0]);
        createParent("/tasks", new byte[0]);
        createParent("/status", new byte[0]);//new byte[0] 表示傳入空數(shù)據(jù)
    }

void createParent(String path, byte[] data){
        zk.create(path,
                data,
                Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT,
                createParentCallback,
                data);// A----這里將data作為上下文傳入到回調函數(shù)中,以便在下面CONNECTIONLOSS時再遞歸調用createParent函數(shù)逗堵。
    }

StringCallback createParentCallback = new StringCallback() {
    public void processResult(int rc, String path, Object ctx, String name) {
        switch (Code.get(rc)) {
        case CONNECTIONLOSS:
            /*
             * Try again. Note that registering again is not a problem.
             * If the znode has already been created, then we get a
             * NODEEXISTS event back.
             */
            createParent(path, (byte[]) ctx);//呼應A

            break;
        case OK:
            LOG.info("Parent created");

            break;
        case NODEEXISTS:
            LOG.warn("Parent already registered: " + path);

            break;
        default:
            LOG.error("Something went wrong: ",
                    KeeperException.create(Code.get(rc), path));
        }
    }
};

注冊從節(jié)點

Zookeeper會嚴格維護執(zhí)行順序秉氧,并提供強有力的有序保障。然而在多線程下還需要小心面對順序問題:比方說遇到異常重發(fā)請求時蜒秤,重發(fā)請求可能排在其他線程請求后面了汁咏。

處理狀態(tài)變化(監(jiān)視點+通知)

引言

輪詢(不好的方式)

例如監(jiān)視主節(jié)點崩潰, 假設備份節(jié)點以50ms/次的頻率積極輪詢, 那么輪詢的請求量= (50ms*備份節(jié)點個數(shù)n)/次。雖然Zookeeper可以很輕松處理這些請求作媚,但主節(jié)點崩潰的情況很少發(fā)生梆暖,這些請求其實是多余的。

監(jiān)視點(改進方式)

通過監(jiān)視點(watch)掂骏,客戶端可以對指定的znode節(jié)點注冊一個通知請求,在發(fā)生變化時就會收到一個單次的通知厚掷。

如何檢測主節(jié)點崩潰
  1. 主節(jié)點在zookeeper樹上創(chuàng)建一個臨時節(jié)點,來標示主節(jié)點鎖。
  2. 備份節(jié)點注冊一個監(jiān)視器來監(jiān)視這個鎖是否存在
  3. 如果主節(jié)點崩潰山宾,這個臨時節(jié)點就會自動刪除择吊;同時備份節(jié)點會受到通知,它們就可以開始進行主節(jié)點選舉县匠。
如何檢測主節(jié)點崩潰

案例:單次觸發(fā)器

術語 說明
事件(event) 一個znode節(jié)點執(zhí)行了更新操作
監(jiān)視點(watch) 如:znode節(jié)點被賦值,或被刪除
通知(notification) 注冊了監(jiān)視點的應用客戶端收到的事件報告消息
  • 監(jiān)視點是一次性的觸發(fā)器: 例如,對/master設置監(jiān)視點后,/master節(jié)點數(shù)據(jù)被修改了了芒划,客戶端會收到一次通知, 但下次數(shù)據(jù)再變化,客戶端就不會再收到通知了欧穴。除非再次設置監(jiān)視點民逼。
  • 當一個zookeeper客戶端與服務端A斷開連接后(注意,并非會話過期涮帘,會話過期會刪除監(jiān)視點)拼苍,連接到服務端B〉饔В客戶端會給B發(fā)送未觸發(fā)的監(jiān)視點列表疮鲫。B會檢查列表中注冊監(jiān)視點后是否有變化,如有弦叶,通知客戶端俊犯。沒有,注冊監(jiān)視點

事件丟失

  • 事件是可能丟失的伤哺,但并不是問題燕侠。
  • 事件丟失: 一個事件A發(fā)生,通知客戶端默责。在客戶端繼續(xù)添加監(jiān)視點之前贬循,又發(fā)生了事件B,此時沒有監(jiān)視點桃序,事件B不會發(fā)送通知杖虾,于是丟失
  • 為什么不是問題: 下一個監(jiān)視點設置的時候,可以查看最新的狀態(tài)(終態(tài))媒熊。中間的變化不用關心
事件丟失

批量通知

多個事件分攤到一個通知上具有積極作用奇适,比如進行高頻率的更新操作時,就比較輕量了芦鳍。

如何設置監(jiān)視點

Zookeeper的API中所有讀操作: getData,getChildren,exists均可以設置監(jiān)視點

// API
public byte[] getData(final String path,Watcher watcher(自定義監(jiān)視點),Stat stat)
public byte[] getData(String path,boolean watch(true=使用默認監(jiān)視點),Stat stat)

// API中watcher的實現(xiàn)類
public class Master implements Watcher {

    @Override
    public void process(WatchedEvent watchedEvent) {
        System.out.println("監(jiān)控: "+watchedEvent);
    }
}

WatchedEvent的數(shù)據(jù)結構

public class WatchedEvent {
    private final KeeperState keeperState;// 會話狀態(tài),枚舉類型: Disconnected,SyncConnected,AuthFailed,ConnectedReadOnly,SaslAuthenticated,Expired
    private final EventType eventType;// 事件類型, 枚舉: NodeCreated,NodeDeleted,NodeDataChanged,NodeChildrenChanged,None(無事件發(fā)生嚷往,而是Zookeeper的會話狀態(tài)發(fā)生了變化)
    private String path;//事件類型不是None時,返回一個znode路徑
}
  • 監(jiān)視點有兩種類型
    • 數(shù)據(jù)監(jiān)視點: exists,getData可以設置數(shù)據(jù)監(jiān)視點
    • 子節(jié)點監(jiān)視點: getChildren可以設置子節(jié)點監(jiān)視點柠衅,這種監(jiān)視點只在znode子節(jié)點創(chuàng)建或刪除時才被觸發(fā)
事件類型 可設置該事件監(jiān)視點的API
NodeCreated exists
NodeDeleted exists,getData
NodeDataChanged exists,getData
NodeChildrenChanged getChildren
  • 監(jiān)視點一旦設置就無法移除皮仁,如果要移除,只有兩個方法
    • 觸發(fā)這個監(jiān)視點
    • 使其會話被關閉或過期

API的普遍模型

以下以exists為例,介紹API的普遍模型贷祈,該框架使用非常廣泛

// 異步調用方式
zk.exists("/myZnode",myWatcher,existsCallback,null應用上下文趋急,會作為callback參數(shù));

Watcher myWatcher = new Watcher(){
    public void process(WatchedEvent e){
        // Watcher的實現(xiàn)
    }
}

StatCallback existsCallback = new StatCallback(){
    public void processResult(int rc, String path, Object ctx, Stat stat){
        //exists的回調對象
    }
}

故障處理

故障發(fā)生點

  • Zookeeper服務
  • 網絡
  • 應用

可恢復的故障場景

場景 說明
正常情況 客戶端在從Zookeeper獲得響應時,可以非呈铺埽肯定它與其它客戶端獲得的響應均保持一致
客戶端與Zookeeper服務連接丟失時 客戶端會使用Disconnected事件和ConnectionLossException異常來表示自己無法了解當前的系統(tǒng)狀態(tài)

Disconnected事件和ConnectionLossException異常

處理
  1. 客戶端會不斷嘗試重新連接另一個Zookeeper服務器呜达,直到重新建立了會話
  2. 一旦會話重新建立,Zookeeper會做如下事情:
    1. 產生一個SyncConnected事件粟耻,并開始處理請求
    2. 注冊之前已經注冊過的監(jiān)視點查近,對期間發(fā)生的變更產生監(jiān)視點事件
典型原因

一個典型原因是因為Zookeeper服務器故障

連接丟失的例子
影響和處理

連接丟失時,如果客戶端還存在進行中的請求要處理挤忙,就會產生很大的影響霜威,開發(fā)中要謹慎處理

  • 連接丟失時,雖然會返回ConnectionLossException和CONNECTIONLOSS返回碼饭玲,但客戶端無法通過這些異常和返回碼來判斷請求是否已經被處理
  • 客戶端開發(fā)時侥祭,需要正確處理連接丟失的情況,以保證最小的系統(tǒng)開銷和損壞茄厘。而不是簡單處理(如重啟客戶端)
舉例
  • 以下是出現(xiàn)兩個群首的例子矮冬,如果開發(fā)不仔細,系統(tǒng)中會出現(xiàn)兩個群首次哈。
  • 因此胎署,當一個進程收到Disconnected事件時,在重新連接之前窑滞,進程需要掛起群首的操作琼牧。
  • 客戶端失去連接一段時間,客戶端開發(fā)者通常會選擇關閉會話哀卫。有一點需要注意巨坊,由于此時已失去連接了,Zookeeper服務端并不會感知客戶端關閉了會話此改,服務端依然會等待會話過期時間過去后才聲明會話已過期
FAQ

當網絡中斷持續(xù)一段時間趾撵,客戶端連接另一個服務器可能會發(fā)生一個長延時。為什么客戶端沒有網絡中斷的某一個時刻(如2倍會話超時時間)作出判斷共啃,而一直連接那個超時的服務器呢占调?

  1. Zookeeper將這種策略問題的決定權交給開發(fā)者處理(即程序員自己寫代碼處理),而不是客戶端API移剪。因為開發(fā)者可以很容易實現(xiàn)關閉句柄這種策略
  2. Zookeeper集群集體停機導致的網絡中斷究珊,時間凍結,然而恢復后纵苛,會話超時時間被重置剿涮,因此會出現(xiàn)較長的延遲言津。對于Zookeeper集群故障,客戶端是不需要做額外處理的幔虏。
  3. 總之纺念,對于網絡中斷的情況,開發(fā)者應該根據(jù)自己的實際情況選擇自己關閉會話想括,而不是依賴客戶端API(它才不管你)
Disconnected導致的錯過監(jiān)視點事件的特殊情況
  • Disconnected恢復后,Zookeeper客戶端庫會在會話重新建立時烙博,建立所有已經存在的監(jiān)視點
  • 然后客戶端會發(fā)送監(jiān)視點列表和最后已知的zxid(最終狀態(tài)的時間戳)瑟蜈,服務器會接受并檢查監(jiān)視點列表中各znode節(jié)點的修改時間戳,如果晚于zxid渣窜,服務器就會觸發(fā)這個監(jiān)視點
  • 每個Zookeeper操作都完全符合以上邏輯铺根。除了exists,因為exists可以在一個不存在的節(jié)點上設置監(jiān)視點
  • 因此exists會導致如下一種錯過監(jiān)視點事件的特殊情況乔宿,解決方案是:盡量避免監(jiān)視一個znode節(jié)點的創(chuàng)建事件位迂。如果一定要監(jiān)視創(chuàng)建事件,也要選擇監(jiān)視存活時期更長的znode節(jié)點
Disconnected恢復后详瑞,自動重連處理的危害
  • 通常掂林,Disconnected恢復后,有些Zookeeper封裝庫通過簡單的補發(fā)命令自動處理連接丟失的故障
  • 有時候會導致錯誤的結果坝橡,比如:在建立/leader節(jié)點時泻帮,出現(xiàn)Disconnected。重連后计寇,再執(zhí)行建立/leader節(jié)點就可能出錯锣杂,因為其它節(jié)點可能已經建立了/leader

不可恢復的故障

Zookeeper丟棄會話的情況

  • 會話過期
  • 已認證的會話無法再次與Zookeeper完成認證

丟棄會話的影響

  • 會話被意外丟棄,會導致臨時性節(jié)點丟失

處理方式

  • 最簡單的方法就是終止進程并重啟番宁。這樣可以通過一個新的會話重新初始化自己的狀態(tài)
  • 如果不重啟元莫,首先必須要清楚與舊會話關聯(lián)的應用內部的狀態(tài)信息,然后重新初始化新的狀態(tài)

從不可恢復故障自動恢復的危害

現(xiàn)在Zookeeper句柄與會話之間是一對一的關系蝶押,早先的Zookeeper不是踱蠢,所以會出現(xiàn)這種情況: 舊句柄會話是群首,新句柄使用同一個會話操作只有群首有權操作的數(shù)據(jù)播聪。

群首選舉和外部資源

  • 只要客戶端與Zookeeper進行任何交互操作朽基,Zookeeper都會保持同步。注意前提是: 客戶端與Zookeeper進行了交互
  • 如果客戶端與Zookeeper沒有交互离陶,則Zookeeper無法保證上述視圖的一致性
  • 沒有交互的情況舉例:
    • 客戶端服務器過載稼虎,導致無法及時發(fā)送心跳,進而導致會話超時招刨,客戶端卻仍然以為自己仍然是主節(jié)點
  • 一個很普遍的資源中心化管理方法(用來確保一致性): Zookeeper確保每次只有一個主節(jié)點可以獨占訪問一個外部資源

協(xié)調外部資源的一個棘手問題

下圖展示了在協(xié)調外部資源(如DB)時出現(xiàn)的一個很棘手的問題霎俩。(超載,時鐘偏移都可能導致該問題)

解決方案:

  • 確保應用不會在超載或時鐘偏移的環(huán)境中運行: 小心監(jiān)控系統(tǒng)負載;良好設計的多線程應用打却;時鐘同步程序保證時鐘同步

  • 使用一種名為隔離的技巧杉适,分布式系統(tǒng)常常使用這種方法用于確保資源的獨占訪問(即連接外部資源時使用一個版本號,如果外部資源已經接收到更高版本的隔離符號柳击,則請求或連接就會被拒絕)猿推。具體做法是:使用Stat結構成員變量czixd,它表示創(chuàng)建該節(jié)點時的zxid捌肴,zxid為唯一的單調遞增序列號蹬叭,所以可以使用czxid作為一個隔離符號

Zookeeper內部原理

請求,事物,標識符

請求類型

請求類型 舉例 處理方式 說明
只讀請求 exists,getData,getChildren Zookeeper服務器會在本地(群首或從節(jié)點)處理 因為在本地處理,所以Zookeeper在處理以只讀請求為主要負載時状知,性能會很秽五。增加更多服務器到集群,可大幅提高整體處理能力饥悴,處理更多讀請求
寫請求 create,delete,setData 將會被轉發(fā)給群首處理 -

事務

以對/z節(jié)點setData為例, 事務= 新的數(shù)據(jù)字段+ 新的版本號坦喘。處理該事務時,服務端會用事務中的數(shù)據(jù)信息替換/z節(jié)點中原有的數(shù)據(jù)信息西设,并會用事務中的版本號更新該節(jié)點版本號(而不是簡單對節(jié)點版本號+1)

  • 一個事務就是一個單位瓣铣,不會被其它事務干擾。(早期济榨,Zookeeper通過服務器單線程來保證事務順序執(zhí)行坯沪,后來增加了多線程支持)
  • 事務還具有冪等性。即可以對同一個事務執(zhí)行多次

標識符

群首產生了一個事務擒滑,就會為該事務分配一個標識符腐晾,稱之為Zookeeper會話ID(zxid), 通過zxid可以判斷執(zhí)行順序

  • zxid = 時間戳+計數(shù)器

群首選舉

群首為集群中的服務器選擇出來的一個服務器,并會一直被集群認可丐一。群首的作用: 對客戶端寫請求進行排序, 并轉換為事務

單個服務器啟動過程

單個服務器啟動過程

選主投票

  • 群首選舉通知消息(leader election notifications)

    • 當一個服務器進入LOOKING狀態(tài)藻糖,就會向集群中每個服務器發(fā)送一個通知
    • 一個服務器發(fā)送的投票信息為: (1,5), 表示該服務器sid為1,最近執(zhí)行的事務zxid為5
    • 出于群首選舉的目的库车,zxid只有一個數(shù)組洋满,而其他協(xié)議中阵漏,zxid則還有時間戳epoch和計數(shù)器
    投票
    • 當一個服務器接收到仲裁數(shù)量的服務器發(fā)來的投票都一樣時裆泳,就表示群首選舉成功蝗柔;如果被選舉的群首為自己,則該服務器行使群首角色,否則就成為追隨者并嘗試連接群首(不一定連接成功,一旦連接成功,則會進行狀態(tài)同步,在同步完成后,追隨者才可以處理新的請求)

選主過程

  • Zookeeper實現(xiàn)選主的Java類為QuorumPeer,超時時間定義在FastLeaderElection中(200ms), 如果要自定義選主實現(xiàn)算法,可實現(xiàn)quorum包中的Election接口
選主過程
選主過程-誤判的情況

Zab協(xié)議

  • Zab: Zookeeper原子廣播協(xié)議(Zookeeper Atomic Broadcast protocol)
  • 在接收到一個寫請求后锋爪,追隨者會將請求轉發(fā)給群首,群首將探索性地執(zhí)行該請求,并將執(zhí)行結果以事務的方式對狀態(tài)進行廣播
  • 引入Zab協(xié)議,以幫助服務器確認一個事務是否已經提交
通過Zab提交事務
Zab對下列事項提供保障 說明
如果群首按順序廣播事務T1,T2为迈。那么每個服務器在提交T2前保證T1已經提交完成 保證事務在服務器之間傳送順序一致
如果某個服務器按照T1,T2的順序提交事務,所有其他服務器也必然會在T2前提交T1 保證服務器不會跳過任何事務

多群首問題

  • Zookeeper使用時間戳來解決多群首問題(集群中出現(xiàn)多個群首),zxid第一個元素為時間戳信息。
多群首問題
  • 阻止系統(tǒng)中同時出現(xiàn)多個群首是非常困難的捏题,因此廣播協(xié)議不能基于以上假設循狰。為了解決這個問題关炼,Zab協(xié)議提供了以下保障
Zab對下列事項提供保障 說明
群首確保提交完所有之前的時間戳內需要提交的事務色鸳,才開始廣播新事務 1.群首不會馬上處于活動狀態(tài)吏砂,直到確保仲裁數(shù)量的服務器認可這個群首新的時間戳
2.一個時間戳的最初狀態(tài)氛雪,必須包含之前已經提交的事務井氢,或已經被其他服務器接受约急,但尚未提交完成的事務
3.如果一個提案消息時間戳為4奴饮,但它在新群首處理第一個提案消息(時間戳6)之前沒有提交,那么舊提案將永遠不會被提交
任何時間點危彩,都不會出現(xiàn)兩個被仲裁支持的群首 保證服務器不會跳過任何事務
  • 群首發(fā)生重疊的情況: [舊群首失效----新群首生效]期間谒府,仲裁服務器中有一個服務器還未追隨新群首盛龄,因此它接收舊群首的提案A匿值,其它的服務器追隨新群首,而未接收提案A喷楣。此時事務A依然會提交叽讳。因為新群首在生效前會學習舊仲裁服務器之前接受的所有提議,并保證他們不會再接收來自舊群首的提議
  • 時間戳發(fā)生轉換時渐逃,Zookeeper使用兩種不同的方式來更新追隨者優(yōu)化這個過程
    • 追隨者之后群首不多:群首只需發(fā)送缺失的事務點
    • 滯后太多:發(fā)送群首擁有的最新完整快照

觀察者

  • 服務器類型:群首雹食,追隨者埠通,觀察者
  • 觀察者码撰,追隨者共同點是:提交來自群首的提議轩性;不同點是:觀察者不參與選舉
  • 引入觀察者的目的:
    • 提高讀請求的可擴展性。寫操作吞吐率與仲裁者數(shù)量成反比狠鸳,引入追隨者能提高讀服務器數(shù)量揣苏,而不改變仲裁者數(shù)量,從而不降低寫操作吞吐率件舵,提高讀操作效率
    • 進行跨多個數(shù)據(jù)中心部署卸察。引入觀察者后,更新請求能夠以高吞吐率和低延遲的方式在一個數(shù)據(jù)中心進行铅祸,接下來再傳播到異地的其他數(shù)據(jù)中心得到執(zhí)行

服務器的構成

  • 服務器:群首坑质,追隨者,觀察者根本上都是服務器临梗。
  • 處理流水線: 處理一個請求的一系列過程
  • 請求處理器:處理流水線上的每一個過程就是請求處理器
獨立服務器流水線
群首服務器流水線
追隨者服務器流水線
觀察者服務器流水線

本地存儲

SyncRequestProcessor處理器用于在處理提議時寫入日志和快照涡扼。在接受一個提議時,一個服務器(追隨者或群首服務器)就會將提議的事務持久化到日志中盟庞,該事務日志保存在服務器的本地磁盤中

日志和磁盤的使用

為了寫日志更快吃沪,使用一下兩種手段

  • 組提交: 即事務先放在內存隊列中,定期flush到磁盤什猖。SyncRequestProcessor.run()
  • 補白: 在文件中預分配磁盤存儲塊票彪,而不是每次寫到文件結尾時,文件系統(tǒng)都需要分配一個新的存儲塊
  • 為避免受到系統(tǒng)中其他寫操作干擾不狮,強烈推薦將事務日志寫入到一個獨立的磁盤降铸,與 操作系統(tǒng)文件,快照文件分開

快照

  • 快照是Zookeeper數(shù)據(jù)樹的拷貝副本摇零,每個服務器會經常以序列化整個樹的方式來提取快照推掸,并保存到文件中
  • 服務器進行快照時不需要進行協(xié)作,也不需要暫停處理請求。
  • 如果快照過程中终佛,數(shù)據(jù)樹由于處理請求而發(fā)生變化俊嗽,那么稱這樣的快照是模糊的,它不能反映出任意給定的時間點數(shù)據(jù)樹的準確狀態(tài)

服務器與會話

  • 獨立模式下
    • 單個服務器跟蹤和維護所有會話(SessionTracker類 和 SessionTrackerImpl類)
  • 仲裁模式下
    • 群首服務器跟蹤和維護會話(和獨立模式的服務器一樣铃彰,都是SessionTracker類 和 SessionTrackerImpl類)
    • 追隨者服務器僅僅是簡單地把客戶端連接的會話信息轉發(fā)給群首(LearnerSessionTracker類)
  • 為保證會話存活绍豁,服務器需要接收會話的心跳信息,心跳的形式有:

    • 新的請求
    • 顯示的ping消息(LearnerHandler.run())
    • 以上兩種情況,服務器通過更新會話的過期時間來出發(fā)會話活躍(SessionTrackerImpl.touchSession())
    • 仲裁模式下牙捉,群首服務器發(fā)送一個ping給追隨者們竹揍,追隨者返回上次ping之后的一個session列表
  • 管理會話過期

    • Zookeeper將"會話過期(名詞)"維護在過期隊列(expiry queue)中,它分為多個bucket


    • 使用bucket模式來管理的主要原因是減少讓會話過期這項工作的系統(tǒng)開銷邪铲。以適當?shù)募毩6葋頇z查會話過期

服務器與監(jiān)視點

服務器與監(jiān)視點

客戶端

客戶端庫主要有兩個類

  • Zookeeper類
    • 寫客戶端代碼時芬位,需要實例化該類來建立一個會話。
    • 一旦建立了一個會話带到,Zookeeper會使用服務端生成的會話標識符(SessionTrackerImpl類)來關聯(lián)該會話
  • ClientCnxn類
    • 管理連接到server的Socket連接
    • 該類維護一個可連接的Zookeeper服務器列表昧碉,在連接斷掉時無縫切換到其他服務器(使用同一個會話)
    • 重連會重置所有監(jiān)視點到新服務器(ClientCnxn.SendThread.primeConnection()), 可使用disableAutoWatchReset禁用(默認開啟)

序列化

  • 使用場合
    • 網絡傳輸
    • 磁盤保存
  • Zookeeper使用了Hadoop中的Jute來做序列化(org.apache.jute)
  • Jute定義文件為zookeeper.jute,它包含所有的消息定義和文件記錄

運行Zookeeper

配置Zookeeper服務器

  • Zookeeper配置文件為: zoo.cfg, 很多參數(shù)也可以通過java的系統(tǒng)屬性傳遞, 如-Dzookeeper.propertyName
  • data目錄: 它可以保存一些差異化文件揽惹,其中myid文件用于區(qū)分各個服務器
key 說明
基本配置 -
clientPort 給客戶端連接的端口號被饿,默認2181
dataDir 配置內存數(shù)據(jù)庫保存的模糊快照目錄,id文件也保存在該目錄下. 并不需要配置到一個專用存儲設備上搪搏,快照后臺異步寫入狭握,且不會鎖數(shù)據(jù)庫
dataLogDir 事務日志存儲目錄,事務日志順序同步寫入疯溺,推薦使用專用的日志存儲設備
tickTime 定義Zookeeper使用的基本時間度量單位(毫秒). 默認3000毫秒论颅。最小超時時間=1 tick,客戶端最小會話超時時間= 2 tick囱嫩,更低的tickTime可以更快發(fā)現(xiàn)超時恃疯,但會導致更高的網絡流量(心跳消息)和cpu(會話存儲器處理)
存儲配置 -
preAllocSize 預分配的事務日志文件大小(KB),默認64MB。預分配的目的是減少文件尋址操作的次數(shù)和其它開銷墨闲。每次快照后會重啟一個新的事務日志今妄,因此100次快照會有100*64KB磁盤開銷,如果事務文件平均100B损俭,那么設置100KB比較合適
snapCount 1.每次快照之間的事務數(shù),默認100000(10w).
2.服務器重啟恢復狀態(tài)耗時(A)=讀取快照耗時(B)+快照后所發(fā)生事務的執(zhí)行時間(C)潘酗,使用快照可以減少C杆兵,但會影響服務器性能。
3.集群中仲裁服務器最好不要同時進行快照(將snapCount設置為隨機數(shù))仔夺;
4.如果前一個快照任務正在進行琐脏,后一個快照任務就會等待
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子日裙,更是在濱河造成了極大的恐慌吹艇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昂拂,死亡現(xiàn)場離奇詭異受神,居然都是意外死亡,警方通過查閱死者的電腦和手機格侯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門鼻听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人联四,你說我怎么就攤上這事撑碴。” “怎么了朝墩?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵醉拓,是天一觀的道長。 經常有香客問我收苏,道長亿卤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任倒戏,我火速辦了婚禮怠噪,結果婚禮上,老公的妹妹穿的比我還像新娘杜跷。我一直安慰自己傍念,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布葛闷。 她就那樣靜靜地躺著憋槐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淑趾。 梳的紋絲不亂的頭發(fā)上阳仔,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音扣泊,去河邊找鬼近范。 笑死,一個胖子當著我的面吹牛延蟹,可吹牛的內容都是我干的评矩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阱飘,長吁一口氣:“原來是場噩夢啊……” “哼斥杜!你這毒婦竟也來了虱颗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蔗喂,失蹤者是張志新(化名)和其女友劉穎忘渔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缰儿,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡畦粮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了返弹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锈玉。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖义起,靈堂內的尸體忽然破棺而出拉背,到底是詐尸還是另有隱情,我是刑警寧澤默终,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布椅棺,位于F島的核電站,受9級特大地震影響齐蔽,放射性物質發(fā)生泄漏两疚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一含滴、第九天 我趴在偏房一處隱蔽的房頂上張望诱渤。 院中可真熱鬧,春花似錦谈况、人聲如沸勺美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赡茸。三九已至,卻和暖如春祝闻,著一層夾襖步出監(jiān)牢的瞬間占卧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工联喘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留从诲,地道東北人踪少。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓瓷马,卻偏偏與公主長得像昧捷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子堤框,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容

  • 上來就送干貨 本書的練習以及代碼可以再O’Reilly官網網站本書頁面下載域滥。 相關鏈接 http://www.or...
    snail_knight閱讀 2,155評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)蜈抓,斷路器启绰,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 本文將從系統(tǒng)模型委可、序列化與協(xié)議、客戶端工作原理腊嗡、會話着倾、服務端工作原理以及數(shù)據(jù)存儲等方面來揭示ZooKeeper的技...
    端木軒閱讀 3,804評論 0 42
  • 一、ZooKeeper的背景 1.1 認識ZooKeeper ZooKeeper---譯名為“動物園管理員”燕少。動物...
    algernoon閱讀 9,071評論 1 106
  • 一個真正的寫數(shù)據(jù)流程是怎么樣的卡者?一個真正的讀數(shù)據(jù)流程是怎么樣的?一個真正的同步數(shù)據(jù)流程是怎么樣的客们?從哪里到哪里崇决?什...
    時待吾閱讀 4,017評論 0 14