Zookeeper 參數詳解及原理與優化

Zookeeper 是什麼

 Zookeeper 是一個基於 Google Chubby 論文實現的一款解決分佈式數據一致性問題的開源實現,方便了依賴 Zookeeper 的應用實現 數據發佈 / 訂閱負載均衡服務註冊與發現分佈式協調事件通知集羣管理Leader 選舉、 分佈式鎖和隊列 等功能php

基本概念

集羣角色

 通常的,在分佈式系統中,構成集羣的每一臺機器都有本身的角色,最爲典型的集羣模式就是 Master / Slave 主備模式。在該模式中,咱們把可以處理全部寫操做的機器稱爲 Master 節點,並把全部經過異步複製方式獲取最新數據、提供讀服務的機器稱爲 Slave 節點html

Zookeeper 參數詳解及原理與優化

 而 Zookeeper 中,則是引入了 領導者(Leader)跟隨者(Follower)觀察者(Observer)三種角色 和 領導(Leading)跟隨(Following)觀察(Observing)尋找(Looking) 等相應的狀態。在 Zookeeper 集羣中的經過一種 Leader 選舉的過程,來選定某個節點做爲 Leader 節點,該節點爲客戶端提供服務。而 Follower 和 Observer 節點,則都能提供服務,惟一的區別在於,Observer 機器不參與 Leader 選舉過程 和 寫操做"過半寫成功"策略,Observer只會被告知已經 commit 的 proposal。所以 Observer 能夠在不影響寫性能的狀況下提高集羣的讀性能(詳見下文 「性能優化 - 優化策略 - Observer 模式」 部分)java

Zookeeper 參數詳解及原理與優化

會話

 Session 指客戶端會話。在 Zookeeper 中,一個客戶端會話是指 客戶端服務器之間的一個 TCP 長鏈接。客戶端啓動的時候,會與服務端創建一個 TCP 鏈接,客戶端會話的生命週期,則是從第一次鏈接創建開始算起。經過這個鏈接,客戶端可以經過心跳檢測與服務器保持有效的會話,並向 Zookeeper 服務器發送請求並接收響應,以及接收來自服務端的 Watch 事件通知node

 Session 的 sessionTimeout 參數,用來控制一個客戶端會話的超時時間。當服務器壓力太大 或者是網絡故障等各類緣由致使客戶端鏈接斷開時,Client 會自動從 Zookeeper 地址列表中逐一嘗試重連(重試策略可以使用 Curator 來實現)。只要在 sessionTimeout 規定的時間內可以從新鏈接上集羣中任意一臺服務器,那麼以前建立的會話仍然有效。若是,在 sessionTimeout 時間外重連了,就會由於 Session 已經被清除了,而被告知 SESSION_EXPIRED,此時須要程序去恢復臨時數據;還有一種 Session 重建後的在新節點上的數據,被以前節點上因網絡延遲晚來的寫請求所覆蓋的狀況,在 ZOOKEEPER-417 中被提出,並在該 JIRA 中新加入的 SessionMovedException,使得 用同一個 sessionld/sessionPasswd 重建 Session 的客戶端能感知到,可是這個問題到 ZOOKEEPER-2219 仍然沒有獲得很好的解決linux

Zookeeper 參數詳解及原理與優化

數據模型

 在 Zookeeper 中,節點分爲兩類,第一類是指 構成集羣的機器,稱之爲機器節點;第二類則是指 數據模型中的數據單元,稱之爲數據節點 ZNode。Zookeeper 將全部數據存儲在內存中,數據模型的結構相似於樹(ZNode Tree),由斜槓(/)進行分割的路徑,就是一個 ZNode,例如 /foo/path1。每一個 ZNode 上都會保存本身的數據內容 和 一系列屬性信息git

 ZNode 能夠分爲持久節點(PERSISTENT)臨時節點(EPHEMERAL)兩類。所謂持久節點是指一旦這個 ZNode 被建立了,除非主動進行移除操做,不然這個節點將一直保存在 Zookeeper 上。而臨時節點的生命週期,是與客戶端會話綁定的,一旦客戶端會話失效,那麼這個客戶端建立的全部臨時節點都會被移除。在 HBase 中,集羣則是經過 **/hbase/rs/* 和 /hbase/master 兩個臨時節點,來監控 HRegionServer 進程的加入和宕機 和 HMaster 進程的 Active 狀態**github

 另外,Zookeeper 還有一種 順序節點(SEQUENTIAL)。該節點被建立的時候,Zookeeper 會自動在其子節點名上,加一個由父節點維護的、自增整數的後綴(上限:Integer.MAX_VALUE)。該節點的特性,還能夠應用到 持久 / 臨時節點 上,組合成 持久順序節點(PERSISTENT_SEQUENTIAL) 和 臨時順序節點(EPHEMERAL_SEQUENTIAL)算法

Zookeeper 參數詳解及原理與優化

版本

 Zookeeper 的每一個 ZNode 上都會存儲數據,對應於每一個 ZNode,Zookeeper 都會爲其維護一個叫作 Stat 的數據結構,Stat 中記錄了這個 ZNode 的三個數據版本,分別是 version(當前 ZNode 數據內容的版本),cversion(當前 ZNode 子節點的版本)和 aversion(當前 ZNode 的 ACL 變動版本)。這裏的版本起到了控制 Zookeeper 操做原子性的做用(詳見下文 「源碼分析 - 落腳點 - Zookeeper 樂觀鎖」 部分)shell

 若是想要讓寫入數據的操做支持 CAS,則能夠藉助 Versionable#withVersion 方法,在 setData()的同時指定當前數據的 verison。若是寫入成功,則說明在當前數據寫入的過程當中,沒有其餘用戶對該 ZNode 節點的內容進行過修改;不然,會拋出一個 KeeperException.BadVersionException,以此能夠判斷本次 CAS 寫入是失敗的。而這樣作的好處就是,能夠避免 「併發局部更新 ZNode 節點內容」 時,發生相互覆蓋的問題數據庫

Watcher

 Watcher(事件監聽器)是 Zookeeper 提供的一種 發佈/訂閱的機制。Zookeeper 容許用戶在指定節點上註冊一些 Watcher,而且在一些特定事件觸發的時候,Zookeeper 服務端會將事件通知給訂閱的客戶端。該機制是 Zookeeper 實現分佈式協調的重要特性

Zookeeper 參數詳解及原理與優化

ACL

 相似於 Unix 文件系統,Zookeeper 採用 ACL(Access Control Lists)策略來進行權限控制(使用方式,詳見下文 「經常使用命令 - 執行腳本 - zkCli - 節點操做」 部分;代碼實現,詳見 PrepRequestProcessor#checkACL

經常使用的權限控制

Command Comment
CREATE (c) 建立子節點的權限
READ (r) 獲取節點數據和子節點列表的權限
WRITE (w) 更新節點數據的權限
DELETE (d) 刪除當前節點的權限
ADMIN (a) 管理權限,能夠設置當前節點的 permission
Scheme ID Comment
world anyone Zookeeper 中對全部人有權限的結點就是屬於 world:anyone
auth 不須要 id 經過 authentication 的 user 都有權限
(Zookeeper 支持經過 kerberos 來進行 authencation,也支持 username/password形式的 authentication)
digest username:BASE64 (SHA1(password)) 須要先經過 username:password 形式的 authentication
ip id 爲客戶機的 IP 地址(或者 IP 地址段) ip:192.168.1.0/14,表示匹配前 14 個 bit 的 IP 段
super 對應的 id 擁有超級權限(CRWDA)

IP

編碼
@Before
public void init() throws Exception {
  zoo = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null);
  acls = new ArrayList<>();
  acls.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "10.24.40.178")));
  acls.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "127.0.0.1")));
  aclsNoAuth = new ArrayList<>();
  aclsNoAuth.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "127.0.0.1")));
}
@Test
public void ipAcl() throws Exception {
  if (zoo.exists(IP_PATH, null) != null) zoo.delete(IP_PATH, -1);
  if (zoo.exists(IP_PATH_NO_AUTH, null) != null) zoo.delete(IP_PATH_NO_AUTH, -1);
  zoo.create(IP_PATH, IP.getBytes(), acls, CreateMode.PERSISTENT);
  assertEquals(IP, new String(zoo.getData(IP_PATH, false, null)));
  zoo.create(IP_PATH_NO_AUTH, IP.getBytes(), aclsNoAuth, CreateMode.PERSISTENT);
  try {
    zoo.getData(IP_PATH_NO_AUTH, false, null);
  } catch (KeeperException.NoAuthException e) {
    assertEquals("KeeperErrorCode = NoAuth for ".concat(IP_PATH_NO_AUTH), e.getMessage());
  }
}

Tips: Full code is here.

命令行
$ zkCli.sh -server localhost:2181
  [zk: localhost:2181(CONNECTED) 16] ls /
    [leader, election, zookeeper, origin, ip, auth_test, benchmark]
  [zk: localhost:2181(CONNECTED) 17] ls /ip
    Authentication is not valid : /ip
  [zk: localhost:2181(CONNECTED) 18] getAcl /ip
    'ip,'10.24.40.178
    : cdrwa
    'ip,'127.0.0.1
    : cdrwa
$ zkCli.sh -server 127.0.0.1:2181
  [zk: 127.0.0.1:2181(CONNECTED) 1] ls /ip
    []
  [zk: 127.0.0.1:2181(CONNECTED) 2] get /ip
    ip
    cZxid = 0x10000c43b
    ctime = Tue Aug 22 16:50:37 CST 2017
    mZxid = 0x10000c43b
    mtime = Tue Aug 22 16:50:37 CST 2017
    pZxid = 0x10000c43b
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 2
    numChildren = 0
優缺點

 簡單易用,直接在物理層面,對用戶進行權限隔離;可是,若是不將 127.0.0.1 放入到 IP Acl 列表裏,會給服務端的運維帶來麻煩

Digest

編碼
@Before
public void init() throws Exception {
  zoo = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null);
  zoo.addAuthInfo("digest", "yuzhouwan:com".getBytes());
  zooNoAuth = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null);
}
@Test
public void digestAcl() throws Exception {
  if (zoo.exists(AUTH_PATH_CHILD, null) != null) zoo.delete(AUTH_PATH_CHILD, -1);
  if (zoo.exists(AUTH_PATH, null) != null) zoo.delete(AUTH_PATH, -1);
  zoo.create(AUTH_PATH, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
  try {
    zooNoAuth.create(AUTH_PATH_CHILD, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
  } catch (KeeperException.InvalidACLException e) {
    assertEquals("KeeperErrorCode = InvalidACL for /auth_test/child", e.getMessage());
  }
  zoo.create(AUTH_PATH_CHILD, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
  try {
    zooNoAuth.delete(AUTH_PATH_CHILD, -1);
  } catch (KeeperException.NoAuthException e) {
    assertEquals("KeeperErrorCode = NoAuth for /auth_test/child", e.getMessage());
  }
  assertEquals(AUTH_PATH, new String(zoo.getData(AUTH_PATH, false, null)));
}

Tips: Full code is here.

命令行
$ zkCli.sh -server localhost:2181
  [zk: localhost:2181(CONNECTED) 5] ls /
    [leader, auth_test, election, zookeeper, benchmark, origin]
  [zk: localhost:2181(CONNECTED) 6] ls /auth_test
    Authentication is not valid : /auth_test
  [zk: localhost:2181(CONNECTED) 7] get /auth_test
    Authentication is not valid : /auth_test
  [zk: localhost:2181(CONNECTED) 8] getAcl /auth_test
    'digest,'yuzhouwan:h/j+/wDlblTtA48jnbq8snP1glA=
    : cdrwa
  [zk: localhost:2181(CONNECTED) 9] addauth digest yuzhouwan:true
  [zk: localhost:2181(CONNECTED) 10] get /auth_test
    /auth_test
    cZxid = 0x10000c31e
    ctime = Tue Aug 22 15:26:27 CST 2017
    mZxid = 0x10000c31e
    mtime = Tue Aug 22 15:26:27 CST 2017
    pZxid = 0x10000c31e
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 10
    numChildren = 0
優缺點

 能夠創建角色,按照用戶名、密碼進行權限控制;可是,想要修改某個用戶的密碼,須要對全部的 ACLs 作更改

SASL & Kerberos

環境搭建

單機版

安裝

$ cd ~/install/
$ wget http://archive.apache.org/dist/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
$ wget http://archive.apache.org/dist/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz.md5
# 校驗 MD5
$ head -n 1 zookeeper-3.4.10.tar.gz.md5
  e4cf1b1593ca870bf1c7a75188f09678  zookeeper-3.4.10.tar.gz
$ md5sum zookeeper-3.4.10.tar.gz
  e4cf1b1593ca870bf1c7a75188f09678 *zookeeper-3.4.10.tar.gz
# 對比 MD5 碼一致後進行解壓安裝
$ tar zxvf zookeeper-3.4.10.tar.gz -C ~/software/
$ cd ~/software
$ ln -s zookeeper-3.4.10 zookeeper

配置

cd zookeeper
$ mkdir tmp
$ cp conf/zoo_sample.cfg conf/zoo.cfg
$ mkdir -p /home/zookeeper/data/zookeeper
$ mkdir -p /home/zookeeper/logs/zookeeper
# 更多配置,詳見下文 「經常使用配置」 部分
$ vim conf/zoo.cfg
  tickTime=2000
  initLimit=10
  syncLimit=5
  dataDir=/home/zookeeper/data/zookeeper
  dataLogDir=/home/zookeeper/logs/zookeeper
  clientPort=2181

啓動

$ bin/zkServer.sh start
$ bin/zkServer.sh status
  ZooKeeper JMX enabled by default
  Using config: /home/zookeeper/software/zookeeper/bin/../conf/zoo.cfg
  Mode: standalone
$ bin/zkCli.sh

分佈式

更多配置,詳見下文 「經常使用配置」 部分
$ vim conf/zoo.cfg
  tickTime=2000
  initLimit=10
  syncLimit=5
  dataDir=/home/zookeeper/data/zookeeper
  dataLogDir=/home/zookeeper/logs/zookeeper
  clientPort=2181
  server.1=yuzhouwan01:2281:2282
  server.2=yuzhouwan02:2281:2282
  server.3=yuzhouwan03:2281:2282
# 在各個節點的 dataDir下建立 myid 文件,並對應 zoo.cfg中配置的 id
[zookeeper@yuzhouwan01 ~] echo "1" > /home/zookeeper/data/zookeeper/myid
[zookeeper@yuzhouwan02 ~] echo "2" > /home/zookeeper/data/zookeeper/myid
[zookeeper@yuzhouwan03 ~] echo "3" > /home/zookeeper/data/zookeeper/myid

經常使用命令

四字命令

Command Comment
conf 輸出相關服務配置的詳細信息
cons 列出全部鏈接到服務器的客戶端的徹底的鏈接 / 會話的詳細信息 (包括「接受 / 發送」 的包數量、會話 id 、操做延遲、最後的操做執行等等信息)
envi 輸出關於服務環境的詳細信息 (區別於 conf 命令)
dump 列出未經處理的會話和臨時節點
stat 查看哪一個節點被選擇做爲 Follower 或者 Leader
ruok 測試是否啓動了該 Server,若回覆 imok 表示已經啓動
mntr 輸出一些運行時信息(latency / packets / alive_connections / outstanding_requests / server_state / znode + watch + ephemerals count …)
reqs 列出未經處理的請求
wchs 列出服務器 watch 的簡要信息
wchc 經過 session 列出服務器 watch 的詳細信息(輸出是一個與 watch 相關的會話的列表)
wchp 經過路徑列出服務器 watch 的詳細信息(輸出一個與 session 相關的路徑)
srvr 輸出服務的全部信息(能夠用來檢查當前節點同步完畢集羣數據,處於 Follower 狀態)
srst 重置服務器統計信息
kill 關掉 Server

Tips: <font color="bright turquoise">部分加粗命令對資源消耗比較大,生產環境慎用!</font>

使用方式

安裝 Netcat
# online
$ yum install nc
# offline
$ uname -a
  Linux yuzhouwan 2.6.32-279.el6_sn.7.x86_64 #1 SMP Fri May 27 18:04:25 CST 2016 x86_64 x86_64 x86_64 GNU/Linux
# 搜索rpm包 https://rpmfind.net/linux/rpm2html/search.php?query=nc&submit=Search+...&system=&arch=x86_64
$ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/nc-1.84-24.el6.x86_64.rpm
$ rpm -ivh nc-1.84-24.el6.x86_64.rpm
Netcat 執行
$ echo <four-letter command> | nc 127.0.0.1 2181

DOS <pangu></pangu>***

避免 wchp / wchc 四字命令被 DOS ***利用
$ vim zoo.cfg
  # 4lw.commands.whitelist=*
  4lw.commands.whitelist=stat, ruok, conf, isro

產生的日誌

能夠看到 zk.out 文件中出現 0:0:0:0:0:0:0:1(IPv6 的回送地址,至關於 IPv4 的 127.0.0.1)和 Processing xxxx command 相應的日誌
2017-06-13 23:05:01,998 [myid:5] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@197] - Accepted socket connection from /0:0:0:0:0:0:0:1:40986
2017-06-13 23:05:01,998 [myid:5] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing srvr command from /0:0:0:0:0:0:0:1:40986
2017-06-13 23:05:02,000 [myid:5] - INFO  [Thread-64778:NIOServerCnxn@1007] - Closed socket connection for client /0:0:0:0:0:0:0:1:40986 (no session established for client)

執行腳本

zkServer

啓動
啓動
$ bin/zkServer.sh start
# 查看狀態
$ bin/zkServer.sh status
# 中止服務
$ bin/zkServer.sh stop
# 重啓
$ bin/zkServer.sh restart
排查問題
能夠經過增長 start-foreground 參數來排查失敗緣由
$ bin/zkServer.sh start-foreground
  ZooKeeper JMX enabled by default
  Using config: /home/eagle/software/zookeeper/bin/../conf/zoo.cfg
  Error: Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain
# /home/eagle/software/zookeeper/zookeeper-3.4.10.jar 的問題,從新下載,校驗 md5 正確後,再次安裝便可

zkCli

啓動
$ cd $ZOOKEEPER_HOME
$ bin/zkCli -server <zk host>:2181,<zk host>:2181,<zk host>:2181
節點操做
Command Example Comment
create 建立一個節點
ls 查看當前節點數據
ls2 查看當前節點數據,並能看到更新次數等信息
set 修改節點
get 獲得一個節點,包含數據更新次數等信息
delete 刪除一個節點
rmr 遞歸刪除
history 列出最近的歷史命令
redo &lt;command number: n&gt; redo 1 重作第 n 步命令
stat 打印節點狀態
close 關閉當前鏈接
connect &lt;host&gt;:&lt;port&gt; connect localhost:2181 當 close 當前鏈接或者意外退出後,可在 zkCli命令模式中重連
quit 退出當前鏈接
setAcl &lt;path&gt; &lt;acl: scheme + id + permissions&gt; setAcl /zk world:anyone:cdrw 設置節點權限策略(詳見上文 「基本概念 - ACL」 部分)
getAcl &lt;path&gt; 獲取節點權限策略
addauth &lt;scheme&gt; &lt;auth&gt; addauth digest username:password 節點權限認證
setquota -n -b val &lt;path&gt;
listquota &lt;path&gt; listquota /zookeeper 查看節點的配額
count=5, bytes=-1 /zookeeper 節點個數限額爲 5,長度無限額
delquota [-n or -b]``&lt;path&gt;
sync &lt;path&gt; 強制同步
(因爲 「過半原則」,致使某些 Zookeeper Server 上的數據是舊的,用 sync 命令可強制同步全部的更新操做)
printwatches on off
常見組合(for Kafka)
Command Comment
get /consumers/&lt;topic&gt;/owners 查看 Topic 實時消費的 Group ID
get /consumers/&lt;topic&gt;/offsets/&lt;group id&gt;/&lt;partitionor&gt; 查看 Offset 狀況(ctime:建立時間;mtime:修改時間)

經常使用配置

dataDir

 Zookeeper 保存服務器存儲快照文件的目錄,默認狀況,Zookeeper 將 寫數據的日誌文件也保存在這個目錄裏(default:/tmp/zookeeper)

dataLogDir

 用來存儲服務器事務日誌

clientPort

 客戶端鏈接 Zookeeper 服務器的端口,Zookeeper 會監聽這個端口,接受客戶端的訪問請求(default:2181)

tickTime(SS / CS)

 用來指示 服務器之間或客戶端服務器之間維護心跳機制的 最小時間單元,Session 最小過時時間默認爲兩倍的 tickTime(default:2000ms)

initLimit(LF)

 集羣中的 Leader 節點和 Follower 節點之間初始鏈接時能容忍的最多心跳數(default:5 tickTime)

syncLimit(LF)

 集羣中的 Leader 節點和 Follower 節點之間請求和應答時能容忍的最多心跳數(default:2 tickTime)

minSessionTimeout & maxSessionTimeout

 默認分別是 2 tickTime ~ 20 tickTime,來用控制 客戶端設置的 Session 超時時間。若是超出或者小於,將自動被服務端強制設置爲 最大或者最小

集羣節點

 配置 Zookeeper 集羣中的服務器節點
  格式:`server.<myid>`=`<服務器地址>`:`<LF通信端口>`:`<選舉端口>`
  樣例:`server.1=yuzhouwan:2888:3888`

動態配置

$ vim zoo_replicated1.cfg
  tickTime=2000
  dataDir=/zookeeper/data/zookeeper1
  initLimit=5
  syncLimit=2
  dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic
$ vim zoo_replicated1.cfg.dynamic
  server.1=125.23.63.23:2780:2783:participant;2791
  server.2=125.23.63.24:2781:2784:participant;2792
  server.3=125.23.63.25:2782:2785:participant;2793

監控

採集方式

JMX

遠程鏈接

 Zookeeper 默認支持 JMX 鏈接,可是隻支持本地鏈接

開啓遠程 JMX
$ vim bin/zkServer.sh
  # ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=$JMXLOCALONLY org.apache.zookeeper.server.quorum.QuorumPeerMain"
  ZOOMAIN="-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false org.apache.zookeeper.server.quorum.QuorumPeerMain"
</pre>

Four-letter command

TCP Dump

使用 tcpdump 命令,須要在 root 權限下執行
$ sudo tcpdump tcp port 2181 and host ! 127.0.0.1
  # 下面抓包到的信息,是 Curator 遠程鏈接,並在 /children 命名空間下,建立臨時節點 /yuzhouwan,最終斷開 TCP 鏈接的過程
  14:50:16.192736 IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [S], seq 818611125, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
  14:50:16.192765 IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [S.], seq 3867146147, ack 818611126, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
  14:50:16.193502 IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [.], ack 1, win 256, length 0
  14:50:16.196755 IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [P.], seq 1:50, ack 1, win 256, length 49
  14:50:16.196768 IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [.], ack 50, win 115, length 0
  14:50:16.198618 IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [P.], seq 1:42, ack 50, win 115, length 41
  14:50:16.214597 IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [P.], seq 50:76, ack 42, win 256, length 26
  14:50:16.215109 IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [P.], seq 42:62, ack 76, win 115, length 20

Tips: 比較關心的一個問題是,tcpdump 是否會對性能形成影響?答案是,會的。當過濾上千的 IP 時,已經會影響到服務器性能。主要瓶頸在 BPF Filter,這是一個 O(n)O(n)  線性時間複雜度的算法,能夠考慮 HiPAC 多維樹匹配 替代

指標

Zookeeper 運行狀態(mntr)

Metrics Comment Threshold
zk_version 版本
zk_avg_latency 平均 響應延遲 > 50ms,比上次統計增加超過 20ms,且上一次延遲不爲 0
zk_max_latency 最大 響應延遲
zk_min_latency 最小 響應延遲
zk_packets_received 收包數
zk_packets_sent 發包數
zk_num_alive_connections 活躍鏈接數 > 3000
zk_outstanding_requests 堆積請求數 連續兩次大於 5(粒度:1min)
zk_server_state 主從狀態 由 Leader 變爲 Follower 或 由 Follower 變爲 Leader
zk_znode_count znode 數 > 40000
zk_watch_count watch 數 > 50000
zk_ephemerals_count 臨時節點數
zk_approximate_data_size 近似數據總和大小
zk_open_file_descriptor_count 打開 文件描述符 數
zk_max_file_descriptor_count 最大 文件描述符 數
zk_followers Follower 數
zk_synced_followers 已同步的 Follower 數 連續兩次檢測到未同步的 Follower 節點 (粒度:1min)
zk_pending_syncs 阻塞中的 sync 操做

Tips: 加粗指標,只有 Leader 節點纔會有

實時預警

numenta / nupic

 NuPIC(Numenta Platform for Intelligent Computing,Numenta智能計算平臺) 是一個不同凡響的開源人工智能平臺,它基於一種腦皮質學習算法,即 「層級實時記憶」(Hierarchical Temporal Memory,HTM)。該算法旨在模擬新大腦皮層的工做原理,將複雜的問題轉化爲模式匹配與預測,而傳統的 AI 算法大可能是針對特定的任務目標而設計的
 NuPIC 聚焦於分析實時數據流,能夠經過學習數據之間基於時間的狀態變化(而非閥值設置),對未知數據進行預測,並揭示其中的很是規特性。詳見個人另外一篇博客:人工智能

性能調優

Benchmark(Test First

 brownsys / zookeeper-benchmark (很難找到合適的開源項目,需本身編寫 Benchmark 工具)

優化策略

部署

日誌目錄
  • 快照目錄 dataDir 和 事務日誌目錄 dataLogDir 分離
  • 寫事務日誌的目錄,須要保證目錄空間足夠大,並掛載到單獨的磁盤上(爲了保證數據的一致性, ZooKeeper 在返回客戶端事務請求響應以前, 必需要將這次請求對應的事務日誌刷入到磁盤中 [forceSync 參數控制,default:yes],因此事務日誌的寫入速度,直接決定了 Zookeeper 的吞吐率)
自動日誌清理
autopurge.purgeInterval

 指定清理頻率,單位爲小時(default:0 表示不開啓自動清理)

autopurge.snapRetainCount

 和上面 purgeInterval 參數配合使用,指定須要保留的文件數目(default:3)

$ vim conf/zoo.cfg
  autopurge.snapRetainCount=3
  autopurge.purgeInterval=1
# 注意:Zookeeper 重啓會自動清除 zookeeper.out 日誌,若是有排錯須要,則應先備份好日誌文件
# 若是發現單事務日誌量過大,致使定時清理沒法及時處理,可使用 zkCleanup.sh 進行手動清除
$ cd ~/software/zookeeper1
$ zookeeper1/bin/zkCleanup.sh /home/zookeeper/logs/zookeeper1/version-2/ 3
  Removing file: Aug 9, 2017 12:08:49 PM    /home/zookeeper/logs/zookeeper1/version-2/log.1c00000001
  Removing file: Aug 9, 2017 02:03:33 PM    /home/zookeeper/data/zookeeper1/version-2/snapshot.1c0000ab90
Log4j 滾動日誌
$ cd $ZOOKEEPER_HOME
$ vim conf/log4j.properties
  zookeeper.root.logger=INFO, CONSOLE
  zookeeper.console.threshold=INFO
  zookeeper.log.dir=.
  zookeeper.log.file=zookeeper.log
  zookeeper.log.threshold=DEBUG
  zookeeper.tracelog.dir=.
  zookeeper.tracelog.file=zookeeper_trace.log
  # 能夠調整爲 DaliyRollingFileAppender,天天滾動建立新的日誌文件
  log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
  log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
  log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
$ vim bin/zkServer.sh
  # 增長 ZOO_LOG_DIR 配置
  ZOO_LOG_DIR=$ZOOBINDIR/../log4j
$ vim bin/zkEnv.sh
  # if [ "x${ZOO_LOG4J_PROP}" = "x" ]
  # then
  #     ZOO_LOG4J_PROP="INFO,CONSOLE"
  # fi
  if [ "x${ZOO_LOG4J_PROP}" = "x" ]
  then
      ZOO_LOG4J_PROP="INFO,ROLLINGFILE"
  fi

Observer 模式

做用
對讀請求進行擴展

 經過增長更多的 Observer,能夠接收更多的讀請求流量,卻不會犧牲寫操做的吞吐量(寫操做的吞吐量取決於 quorum 法定人數的個數)
 若是增長更多的 Server 進行投票,Quorum 會變大,這會下降寫操做的吞吐量
 然而增長 Observer 並不會徹底沒有損耗,新的 Observer 在提交一個事務後收到一條額外的 INFORM 消息。這個損耗比加入 Follower 進行投票來講會小不少

Zookeeper 參數詳解及原理與優化

(圖片來源:Observers: core functionality)</center>

跨數據中心部署

 把 participant 分散到多個數據中心,可能會由於數據中心之間的網絡延遲,致使系統被拖慢
 使用 Observer 的話,更新操做都在單獨的數據中心來處理,再發送到其餘數據中心,讓 Client 消費數據(分佈式數據庫 [中美異地機房] 同步系統 Otter 就使用該模式)

Zookeeper 參數詳解及原理與優化

設置
$ vim conf/zoo.cfg
  peerType=observer
  server.1:localhost:2181:3181:observer # 其餘須要擴展成 Observer 的 Server 都須要加上 `:observer` 後綴
INFORM 消息

 由於 Observer 不參與到 ZAB 選舉中,因此 Leader 節點不會發送 proposal 給 Observer,只會發送一條包含已經經過選舉的 zxid 的 INFORM 消息。這裏,參與 ZAB 選舉的 Leader、Follower 節點稱之爲 PARTICIPANT Server,而 Observer 則屬於 OBSERVER Server

配置

JVM 相關
swappiness
$ cd $ZOOKEEEPER_HOME
# 常駐進程,須要避免 swapping 損害性能
# 臨時生效
$ echo 0 > /proc/sys/vm/swappiness
# 永久生效
$ vim /etc/sysctl.conf
  vm.swappiness=0 # memory first
# 設置 `-XX:+AlwaysPreTouch` 參數,在進程啓動的時候,讓 jvm 經過 demand-zeroed 方式將內存一次分配到位(ES #16937 / ZK #301)
# 使用 CMS 垃圾回收器(「jdk7 + 內存使用很少」 的緣故,能夠暫不考慮 G1GC)
$ vim conf/java.env
  export JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC"
  # 若是須要打印 GC 日誌,則多增長一些 flag
  export JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:-PrintGCTimeStamps -Xloggc:/home/zookeeper/logs/zookeeper_`date '+%Y%m%d%H%M%S'`.gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=64M"
  # 須要注意的是,若是不但願 zkCli 等命令建立 gc 日誌文件,須要把 JVMFLAGS 改爲 SERVER_JVMFLAGS
# 更進一步,若是有四字命令在作監控,則建議,直接修改 zkServer.sh,不然由於 zookeeper_`date '+%Y%m%d%H%M%S'`.gc 的存在,致使每次四字命令執行,會有不少小日誌被建立(ZK#302 已解決,待分析)
$ vim bin/zkServer.sh
  start)
    # ...
    START_SERVER_JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:-PrintGCTimeStamps -Xloggc:/home/zookeeper/logs/zookeeper_`date '+%Y%m%d%H%M%S'`.gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=64M"
    nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
    "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \
    -cp "$CLASSPATH" $JVMFLAGS $START_SERVER_JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &
# 堆大小的最終肯定,須要在 benchmark 結果的基礎之上,再作調整
# 另外,一旦建立完該文件,Zookeeper 進程會自動加載,所以,須要確保無誤以後,再創建 java.env 文件
升級 JDK8

 爲什麼建議升級 JDK8 呢?由於 Zookeeper 裏面不少關鍵的功能點,都用到了 Atomic 類,而該類在 JDK8 中作了一次升級,性能提高了 6x 倍(JDK8 中加入了 Unsafe.getUnsafe().getAnd[Add|Set][Int|Long|Object] 一系列方法對 Atomic 類作了加強,因爲沒法看到 Oracle JDK 裏 Unsafe 的相關實現,有興趣能夠參考 OpenJDK 源碼。目前,存在一種比較靠譜的猜想是,compare-and-swap 被替換成系統底層的 fetch-and-add,後者用 lock xadd 替代了 lock cmpxchg 來實現原子操做。其中 指令前綴 lock 用來鎖定指令涉及的存儲區域,xadd 指令做用是 交換兩個操做數的值,再進行加法操做,cmpxchg比較交換指令,第一操做數先和 AL/AX/EAX 比較,若是相等 ZF 置 1,第二操做數賦給第一操做數,不然 ZF 清 0,第一操做數賦給 AL/AX/EAX。因而可知,xadd 指令實現的 FAA 和 cmpxchg 指令實現的 CAS 相比,並無自旋,所以不用擔憂循環時間過長以後 CPU 資源消耗過大,而且也沒有了 CAS 中 ABA 之類的問題 [該問題在 AtomicStampedReference 中,經過增長版本號解決了])

原文地址:https://blog.csdn.net/oASiDuoFu/article/details/80525870
相關文章
相關標籤/搜索