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