zookeeper 簡介

一、ZooKeeper是一個分佈式的、提供高可用的、存放鍵值對的服務

分佈式

Zookeeper提供了分佈式獨享鎖
獲取鎖實現思路:
1.首先創建一個作爲鎖目錄(znode),通常用它來描述鎖定的實體,稱爲:/lock_node
2.希望獲得鎖的客戶端在鎖目錄下創建znode,作爲鎖/lock_node的子節點,並且節點類型爲有序臨時節點(EPHEMERAL_SEQUENTIAL);
  例如:有兩個客戶端創建znode,分別爲/lock_node/lock-1和/lock_node/lock-2
3.當前客戶端調用getChildren(/lock_node)得到鎖目錄所有子節點,不設置watch,接着獲取小於自己(步驟2創建)的兄弟節點
4.步驟3中獲取小於自己的節點不存在 && 最小節點與步驟2中創建的相同,說明當前客戶端順序號最小,獲得鎖,結束。
5.客戶端監視(watch)相對自己次小的有序臨時節點狀態
6.如果監視的次小節點狀態發生變化,則跳轉到步驟3,繼續後續操作,直到退出鎖競爭。


高可用

 通過投票選舉leader


存放鍵值對

Zookeeper是以樹狀結果存放鍵值對的。

zookeeper的4種節點類型:

1、持久節點:節點創建後,會一直存在,不會因客戶端會話失效而刪除;

PERSISTENT (0, false, false), 

2、 持久順序節點:基本特性與持久節點一致,創建節點的過程中,zookeeper會在其名字後自動追加一個單調增長的數字後綴,作爲新的節點名; 

PERSISTENT_SEQUENTIAL (2, false, true), 

3、臨時節點:客戶端會話失效或連接關閉後,該節點會被自動刪除,且不能再臨時節點下面創建子節點,否則報如下錯:org.apache.zookeeper.KeeperException$NoChildrenForEphemeralsException;

EPHEMERAL (1, true, false),

4、臨時順序節點:基本特性與臨時節點一致,創建節點的過程中,zookeeper會在其名字後自動追加一個單調增長的數字後綴,作爲新的節點名; 

EPHEMERAL_SEQUENTIAL (3, true, true);


 每個znode由3部分組成:

stat. 此爲狀態信息, 描述該znode的版本, 權限等信息.

data. 與該znode關聯的數據.

children. 該znode下的子節點.


znode節點的狀態信息

czxid. 節點創建時的zxid.

mzxid. 節點最新一次更新發生時的zxid.
ctime. 節點創建時的時間戳.
mtime. 節點最新一次更新發生時的時間戳.
dataVersion. 節點數據的更新次數.
cversion.        其子節點的更新次數.
aclVersion. 節點ACL(授權信息)的更新次數.
ephemeralOwner. 如果該節點爲ephemeral節點, ephemeralOwner值表示與該節點綁定的session id,
如果該節點不是ephemeral節點, ephemeralOwner值爲0.
dataLength. 節點數據的字節數.
numChildren. 子節點個數.

Data
zookeeper默認對每個結點的最大數據量有一個上限是1M,如果你要設置的配置數據大於這個上限將無法寫法,

增加-Djute.maxbuffer=10240000參數 


持久化

 所有的操作都是存放在事務日誌中的,可以用於數據恢復。

 快照是ZK的data tree的一份拷貝。每一個server每隔一段時間會序列化data tree的所有數據並寫入一個文件。

二、安裝配置

下載 http://zookeeper.apache.org/

配置

cat /opt/oracle/zookeeper/conf/zoo.cfg

dataDir=/opt/oracle/data/zookeeper/data

dataLogDir=/opt/oracle/data/zookeeper/datalog

clientPort=2181

initLimit=10

syncLimit=5

server.1=zookeeper01:2888:3888

server.2=zookeeper02:2888:3888

server.3=zookeeper03:2888:3888


參數名


說明


clientPort 客戶端連接server的端口,即對外服務端口,一般設置爲2181吧。

dataDir 存儲快照文件snapshot的目錄。默認情況下,事務日誌也會存儲在這裏。建議同時配置參數dataLogDir, 事務日誌的寫性能直接影響zk性能。

tickTime ZK中的一個時間單元。ZK中所有時間都是以這個時間單元爲基礎,進行整數倍配置的。例如,session的最小超時時間是2*tickTime。

dataLogDir 事務日誌輸出目錄。儘量給事務日誌的輸出配置單獨的磁盤或是掛載點,這將極大的提升ZK性能。

(No Java system property)

globalOutstandingLimit 最大請求堆積數。默認是1000。ZK運行的時候, 儘管server已經沒有空閒來處理更多的客戶端請求了,但是還是允許客戶端將請求提交到服務器上來,以提高吞吐性能。當然,爲了防止Server內存溢出,這個請求堆積數還是需要限制下的。

(Java system property:zookeeper.globalOutstandingLimit.)

preAllocSize 預先開闢磁盤空間,用於後續寫入事務日誌。默認是64M,每個事務日誌大小就是64M。如果ZK的快照頻率較大的話,建議適當減小這個參數。(Java system property:zookeeper.preAllocSize)

snapCount 每進行snapCount次事務日誌輸出後,觸發一次快照(snapshot), 此時,ZK會生成一個snapshot.*文件,同時創建一個新的事務日誌文件log.*。默認是100000.(真正的代碼實現中,會進行一定的隨機數處理,以避免所有服務器在同一時間進行快照而影響性能)(Java system property:zookeeper.snapCount)

traceFile 用於記錄所有請求的log,一般調試過程中可以使用,但是生產環境不建議使用,會嚴重影響性能。(Java system property:?requestTraceFile)

maxClientCnxns 單個客戶端與單臺服務器之間的連接數的限制,是ip級別的,默認是60,如果設置爲0,那麼表明不作任何限制。請注意這個限制的使用範圍,僅僅是單臺客戶端機器與單臺ZK服務器之間的連接數限制,不是針對指定客戶端IP,也不是ZK集羣的連接數限制,也不是單臺ZK對所有客戶端的連接數限制。指定客戶端IP的限制策略,這裏有一個patch,可以嘗試一下:http://rdc.taobao.com/team/jm/archives/1334(No Java system property)

clientPortAddress 對於多網卡的機器,可以爲每個IP指定不同的監聽端口。默認情況是所有IP都監聽clientPort指定的端口。New in 3.3.0

minSessionTimeoutmaxSessionTimeout Session超時時間限制,如果客戶端設置的超時時間不在這個範圍,那麼會被強制設置爲最大或最小時間。默認的Session超時時間是在2 * tickTime ~ 20 * tickTime這個範圍 New in 3.3.0

fsync.warningthresholdms 事務日誌輸出時,如果調用fsync方法超過指定的超時時間,那麼會在日誌中輸出警告信息。默認是1000ms。(Java system property:fsync.warningthresholdms)New in 3.3.4

autopurge.purgeInterval 在上文中已經提到,3.4.0及之後版本,ZK提供了自動清理事務日誌和快照文件的功能,這個參數指定了清理頻率,單位是小時,需要配置一個1或更大的整數,默認是0,表示不開啓自動清理功能。(No Java system property) New in 3.4.0

autopurge.snapRetainCount 這個參數和上面的參數搭配使用,這個參數指定了需要保留的文件數目。默認是保留3個。(No Java system property)New in 3.4.0

electionAlg 在之前的版本中, 這個參數配置是允許我們選擇leader選舉算法,但是由於在以後的版本中,只會留下一種“TCP-based version of fast leader election”算法,所以這個參數目前看來沒有用了,這裏也不詳細展開說了。(No Java system property)

initLimit Follower在啓動過程中,會從Leader同步所有最新數據,然後確定自己能夠對外服務的起始狀態。Leader允許F在initLimit時間內完成這個工作。通常情況下,我們不用太在意這個參數的設置。如果ZK集羣的數據量確實很大了,F在啓動的時候,從Leader上同步數據的時間也會相應變長,因此在這種情況下,有必要適當調大這個參數了。(No Java system property)

syncLimit 在運行過程中,Leader負責與ZK集羣中所有機器進行通信,例如通過一些心跳檢測機制,來檢測機器的存活狀態。如果L發出心跳包在syncLimit之後,還沒有從F那裏收到響應,那麼就認爲這個F已經不在線了。注意:不要把這個參數設置得過大,否則可能會掩蓋一些問題。(No Java system property)

leaderServes 默認情況下,Leader是會接受客戶端連接,並提供正常的讀寫服務。但是,如果你想讓Leader專注於集羣中機器的協調,那麼可以將這個參數設置爲no,這樣一來,會大大提高寫操作的性能。(Java system property: zookeeper.leaderServes)。

server.x=[hostname]:nnnnn[:nnnnn] 這裏的x是一個數字,與myid文件中的id是一致的。右邊可以配置兩個端口,第一個端口用於F和L之間的數據同步和其它通信,第二個端口用於Leader選舉過程中投票通信。

(No Java system property)

group.x=nnnnn[:nnnnn]weight.x=nnnnn 對機器分組和權重設置,可以 參見這裏(No Java system property)

cnxTimeout Leader選舉過程中,打開一次連接的超時時間,默認是5s。(Java system property: zookeeper.cnxTimeout)

zookeeper.DigestAuthenticationProvider

.superDigest ZK權限設置相關,具體參見《使用super身份對有權限的節點進行操作》 和 《ZooKeeper權限控制》

skipACL 對所有客戶端請求都不作ACL檢查。如果之前節點上設置有權限限制,一旦服務器上打開這個開頭,那麼也將失效。(Java system property:zookeeper.skipACL)

forceSync 這個參數確定了是否需要在事務日誌提交的時候調用FileChannel.force來保證數據完全同步到磁盤。(Java system property:zookeeper.forceSync)

jute.maxbuffer 每個節點最大數據量,是默認是1M。這個限制必須在server和client端都進行設置纔會生效。(Java system property:jute.maxbuffer)



ZooKeeper服務命令:

在準備好相應的配置之後,可以直接通過zkServer.sh 這個腳本進行服務的相關操作

1. 啓動ZK服務:       sh bin/zkServer.sh start

2. 查看ZK服務狀態:   sh bin/zkServer.sh status

3. 停止ZK服務:       sh bin/zkServer.sh stop

4. 重啓ZK服務:       sh bin/zkServer.sh restart




zk客戶端命令

ZooKeeper命令行工具類似於Linux的shell環境,不過功能肯定不及shell啦,但是使用它我們可以簡單的對ZooKeeper進行訪問,數據創建,數據修改等操作. 

 使用 zkCli.sh -server 127.0.0.1:2181 連接到 ZooKeeper 服務,連接成功後,系統會輸出 ZooKeeper 的相關環境以及配置信息。

命令行工具的一些簡單操作如下:

1. 顯示根目錄下、文件: ls / 使用 ls 命令來查看當前 ZooKeeper 中所包含的內容

2. 顯示根目錄下、文件: ls2 / 查看當前節點數據並能看到更新次數等數據

3. 創建文件,並設置初始內容: create /zk "test" 創建一個新的 znode節點“ zk ”以及與它關聯的字符串

4. 獲取文件內容: get /zk 確認 znode 是否包含我們所創建的字符串

5. 修改文件內容: set /zk "zkbak" 對 zk 所關聯的字符串進行設置

6. 刪除文件: delete /zk 將剛纔創建的 znode 刪除 

7. 退出客戶端: quit 

8. 幫助命令: help 




三、zookeeper 的 python api

zookeeper的python客戶端安裝

1.由於python客戶端依賴c的客戶端所以要先安裝c版本的客戶端



cd zookeeper-3.4.5/src/c  

./configure  

make   

make install  


2.測試c版本客戶端

./cli_mt localhost:2181  

Watcher SESSION_EVENT state = CONNECTED_STATE  

Got a new session id: 0x23f9d77d3fe0001  


3、安裝zkpython


 wget --no-check-certificate http://pypi.python.org/packages/source/z/zkpython/zkpython-0.4.tar.gz

 tar xf zkpython-0.4.tar.gz

 cd zkpython-0.4

python setup.py install



watch

watch的意思是監聽感興趣的事件. 在命令行中, 以下幾個命令可以指定是否監聽相應的事件.

ls命令. ls命令的第一個參數指定znode, 第二個參數如果爲true, 則說明監聽該znode的子節點的增減, 以及該znode本身的刪除事件.

ls /test1 true

create /test1/01 123

get命令. get命令的第一個參數指定znode, 第二個參數如果爲true, 則說明監聽該znode的更新和刪除事件.

get /test true

set /test test

stat命令. stat命令用於獲取znode的狀態信息. 第一個參數指定znode, 如果第二個參數爲true, 則監聽該node的更新和刪除事件.




清理數據目錄


快照是ZK的data tree的一份拷貝。每一個server每隔一段時間會序列化data tree的所有數據並寫入一個文件



#!/bin/bash


#snapshot file dir

dataDir=/data/zookeeper/data/version-2

#tran log dir

dataLogDir=/data/zookeeper/datalog/version-2

#zk log dir

logDir=/data/zookeeper/log

#Leave 1 files

count=1

count=$[$count+1]

ls -t $dataLogDir/log.* | tail -n +$count | xargs rm -f

ls -t $dataDir/snapshot.* | tail -n +$count | xargs rm -f

ls -t $logDir/zookeeper.* | tail -n +$count | xargs rm -f



api






zkclient.py


#!/usr/bin/python

# -*- coding: UTF-8 -*-


import zookeeper, time, threading

from collections import namedtuple


zookeeper.set_debug_level(zookeeper.LOG_LEVEL_ERROR)

DEFAULT_TIMEOUT = 30000

VERBOSE = True


ZOO_OPEN_ACL_UNSAFE = {"perms":0x1f, "scheme":"world", "id" :"anyone"}


# Mapping of connection state values to human strings.

STATE_NAME_MAPPING = {

    zookeeper.ASSOCIATING_STATE: "associating",

    zookeeper.AUTH_FAILED_STATE: "auth-failed",

    zookeeper.CONNECTED_STATE: "connected",

    zookeeper.CONNECTING_STATE: "connecting",

    zookeeper.EXPIRED_SESSION_STATE: "expired",

}


# Mapping of event type to human string.

TYPE_NAME_MAPPING = {

    zookeeper.NOTWATCHING_EVENT: "not-watching",

    zookeeper.SESSION_EVENT: "session",

    zookeeper.CREATED_EVENT: "created",

    zookeeper.DELETED_EVENT: "deleted",

    zookeeper.CHANGED_EVENT: "changed",

    zookeeper.CHILD_EVENT: "child",

}


class ZKClientError(Exception):

    def __init__(self, value):

        self.value = value

    def __str__(self):

        return repr(self.value)


class ClientEvent(namedtuple("ClientEvent", 'type, connection_state, path')):

    """

    A client event is returned when a watch deferred fires. It denotes

    some event on the zookeeper client that the watch was requested on.

    """


    @property

    def type_name(self):

        return TYPE_NAME_MAPPING[self.type]


    @property

    def state_name(self):

        return STATE_NAME_MAPPING[self.connection_state]


    def __repr__(self):

        return  "<ClientEvent %s at %r state: %s>" % (

            self.type_name, self.path, self.state_name)



def watchmethod(func):

    def decorated(handle, atype, state, path):

        event = ClientEvent(atype, state, path)

        return func(event)

    return decorated


class ZKClient(object):

    def __init__(self, servers, timeout=DEFAULT_TIMEOUT):

        self.timeout = timeout

        self.connected = False

        self.conn_cv = threading.Condition( )

        self.handle = -1


        self.conn_cv.acquire()

        if VERBOSE: print("Connecting to %s" % (servers))

        start = time.time()

        self.handle = zookeeper.init(servers, self.connection_watcher, timeout)

        self.conn_cv.wait(timeout/1000)

        self.conn_cv.release()


        if not self.connected:

            raise ZKClientError("Unable to connect to %s" % (servers))


        if VERBOSE:

            print("Connected in %d ms, handle is %d"

                  % (int((time.time() - start) * 1000), self.handle))


    def connection_watcher(self, h, type, state, path):

        self.handle = h

        self.conn_cv.acquire()

        self.connected = True

        self.conn_cv.notifyAll()

        self.conn_cv.release()


    def close(self):

        return zookeeper.close(self.handle)


    def create(self, path, data="", flags=0, acl=[ZOO_OPEN_ACL_UNSAFE]):

        start = time.time()

        result = zookeeper.create(self.handle, path, data, acl, flags)

        #if VERBOSE:

        #    print("Node %s created in %d ms"

        #          % (path, int((time.time() - start) * 1000)))

        return result


    def delete(self, path, version=-1):

        start = time.time()

        result = zookeeper.delete(self.handle, path, version)

        if VERBOSE:

            print("Node %s deleted in %d ms"

                  % (path, int((time.time() - start) * 1000)))

        return result


    def get(self, path, watcher=None):

        return zookeeper.get(self.handle, path, watcher)


    def exists(self, path, watcher=None):

        return zookeeper.exists(self.handle, path, watcher)


    def set(self, path, data="", version=-1):

        return zookeeper.set(self.handle, path, data, version)


    def set2(self, path, data="", version=-1):

        return zookeeper.set2(self.handle, path, data, version)



    def get_children(self, path, watcher=None):

        return zookeeper.get_children(self.handle, path, watcher)


    def async(self, path = "/"):

        return zookeeper.async(self.handle, path)


    def acreate(self, path, callback, data="", flags=0, acl=[ZOO_OPEN_ACL_UNSAFE]):

        result = zookeeper.acreate(self.handle, path, data, acl, flags, callback)

        return result


    def adelete(self, path, callback, version=-1):

        return zookeeper.adelete(self.handle, path, version, callback)


    def aget(self, path, callback, watcher=None):

        return zookeeper.aget(self.handle, path, watcher, callback)


    def aexists(self, path, callback, watcher=None):

        return zookeeper.aexists(self.handle, path, watcher, callback)


    def aset(self, path, callback, data="", version=-1):

        return zookeeper.aset(self.handle, path, data, version, callback)


watch_count = 0


"""Callable watcher that counts the number of notifications"""

class CountingWatcher(object):

    def __init__(self):

        self.count = 0

        global watch_count

        self.id = watch_count

        watch_count += 1


    def waitForExpected(self, count, maxwait):

        """Wait up to maxwait for the specified count,

        return the count whether or not maxwait reached.


        Arguments:

        - `count`: expected count

        - `maxwait`: max milliseconds to wait

        """

        waited = 0

        while (waited < maxwait):

            if self.count >= count:

                return self.count

            time.sleep(1.0);

            waited += 1000

        return self.count


    def __call__(self, handle, typ, state, path):

        self.count += 1

        if VERBOSE:

            print("handle %d got watch for %s in watcher %d, count %d" %

                  (handle, path, self.id, self.count))


"""Callable watcher that counts the number of notifications

and verifies that the paths are sequential"""

class SequentialCountingWatcher(CountingWatcher):

    def __init__(self, child_path):

        CountingWatcher.__init__(self)

        self.child_path = child_path


    def __call__(self, handle, typ, state, path):

        if not self.child_path(self.count) == path:

            raise ZKClientError("handle %d invalid path order %s" % (handle, path))

        CountingWatcher.__call__(self, handle, typ, state, path)


class Callback(object):

    def __init__(self):

        self.cv = threading.Condition()

        self.callback_flag = False

        self.rc = -1


    def callback(self, handle, rc, handler):

        self.cv.acquire()

        self.callback_flag = True

        self.handle = handle

        self.rc = rc

        handler()

        self.cv.notify()

        self.cv.release()


    def waitForSuccess(self):

        while not self.callback_flag:

            self.cv.wait()

        self.cv.release()


        if not self.callback_flag == True:

            raise ZKClientError("asynchronous operation timed out on handle %d" %

                             (self.handle))

        if not self.rc == zookeeper.OK:

            raise ZKClientError(

                "asynchronous operation failed on handle %d with rc %d" %

                (self.handle, self.rc))



class GetCallback(Callback):

    def __init__(self):

        Callback.__init__(self)


    def __call__(self, handle, rc, value, stat):

        def handler():

            self.value = value

            self.stat = stat

        self.callback(handle, rc, handler)


class SetCallback(Callback):

    def __init__(self):

        Callback.__init__(self)


    def __call__(self, handle, rc, stat):

        def handler():

            self.stat = stat

        self.callback(handle, rc, handler)


class ExistsCallback(SetCallback):

    pass


class CreateCallback(Callback):

    def __init__(self):

        Callback.__init__(self)


    def __call__(self, handle, rc, path):

        def handler():

            self.path = path

        self.callback(handle, rc, handler)


class DeleteCallback(Callback):

    def __init__(self):

        Callback.__init__(self)


    def __call__(self, handle, rc):

        def handler():

            pass

        self.callback(handle, rc, handler)

if __name__ == '__main__':

    zk=ZKClient('10.10.79.185:2181,10.10.79.184:2181,10.10.79.183:2181')

    zk.create('/test1','123')

    zk.close


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章