http://www.tuicool.com/articles/6ZvaQzQ
Zookeeper是一個分布式協調框架,有不錯的性能,也經過許多公司的驗證,所以在很多場景都有使用。大家一般用Zookeeper來實現服務發現(類似DNS),配置管理,分布式鎖,leader選舉等。在這些場景中,Zookeeper成為了一個被依賴的核心組件,Zookeeper的穩定性是需要特別關注的。
去哪兒網也在很多場景依賴Zookeeper,所以我們也一直在摸索怎么更好的運維穩定的Zookeeper集群。在過去的幾年我們也踩過一些坑,也因為Zookeeper導致了故障。現在將我們運維Zookeeper集群的一些經驗分享,也歡迎大家提供更好的建議。
那么在打算運維一套Zookeeper集群之前,我們先了解一些Zookeeper的基本原理。
集群里分三種角色: Leader, Follower和Observer。Leader和Follower參與投票,Observer只會『聽』投票的結果,不參與投票。
投票集群里的節點數要求是奇數
一個集群容忍掛掉的節點數的等式為 N = 2F + 1,N為投票集群節點數,F為能同時容忍失敗節點數。比如一個三節點集群,可以掛掉一個節點,5節點集群可以掛掉兩個...
一個寫操作需要半數以上的節點ack,所以集群節點數越多,整個集群可以抗掛點的節點數越多(越可靠),但是吞吐量越差。
Zookeeper里所有節點以及節點的數據都會放在內存里,形成一棵樹的數據結構。并且定時的dump snapshot到磁盤。
Zookeeper的Client與Zookeeper之間維持的是長連接,并且保持心跳,Client會與Zookeeper之間協商出一個Session超時時間出來(其實就是Zookeeper Server里配置了最小值,最大值,如果client的值在這兩個值之間則采用client的,小于最小值就是最小值,大于最大值就用最大值),如果在Session超時時間內沒有收到心跳,則該Session過期。
Client可以watch Zookeeper那個樹形數據結構里的某個節點或數據,當有變化的時候會得到通知。
有了這些了解后,那么我們心里其實有底了。
1. 最小生產集群
要確保Zookeeper能夠穩定運行,那么就需要確保投票能夠正常進行,最好不要掛一個節點整個就不work了,所以我們一般要求 最少5個節點部署 。
2. 網絡
除了節點外,我們還要看不能一臺物理機器,一個機柜或一個交換機掛掉然后影響了整個集群,所以節點的 網絡結構也要考慮 。這個可能就比很多應用服務器的要求更加嚴格。
3. 分Group,保護核心Group
要確保Zookeeper整個集群可靠運行,就是要確保投票集群可靠。那在我們這里,將一個Zookeeper集群劃分為多個小的Group ,我們稱Leader+Follower為核心Group,核心Group我們一般是不向外提供服務的,然后我們會根據不同的業務再加一些Observer,比如一個Zookeeper集群為服務發現,消息,定時任務三個不同的組件提供服務,那么我們為建立三個Observer Group,分別給這三個組件使用,而Client只會連接分配給它的Observer Group,不去連接核心Group。這樣核心Group就不會給Client提供長連接服務,也不負責長連接的心跳,這大大的減輕了核心Group的壓力,因為在實際環境中,一個Zookeeper集群要為上萬臺機器提供服務,維持長連接和心跳還是要消耗一定的資源的。因為Observer是不參與投票的所以加Observer并不會降低整體的吞吐量,而且Observer掛掉不會影響整個集群的健康。
但是這里要注意的是,分Observer Group只能解決部分問題,因為畢竟所有的寫入還是要交給核心Group來處理的,所以對于寫入量特別大的應用來說,還是需要進行集群上的隔離,比如Storm和Kafka就對Zookeeper壓力比較大,你就不能將其與服務發現的集群放在一起。
4. 內存
因為Zookeeper將所有數據都放在內存里,所以對JVM以及機器的內存也要預先計劃,如果出現Swap那將嚴重的影響Zookeeper集群的性能,所以我一般不怎么推薦將Zookeeper用作通用的配置管理服務。因為一般配置數據還是挺大的,這些全部放在內存里不太可控。
5. 日志清理
因為Zookeeper要頻繁的寫txlog(Zookeeper寫的一種順序日志)以及定期dump內存snapshot到磁盤,這樣磁盤占用就越來越大,所以Zookeeper提供了清理這些文件的機制,但是這種機制并不太合理,它只能設置間隔多久清理,而不能設置具體的時間段。那么就有可能碰到高峰期間清理,所以建議將其關閉:autopurge.purgeInterval=0。然后使用crontab等機制,在業務低谷的時候清理。
6. 日志,jvm配置
從官網直接下載的包如果直接啟動運行是很糟糕的,這個包默認的配置日志是不會輪轉的,而且是直接輸出到終端。我們最開始并不了解這點,然后運行一段時間后發現生成一個龐大的zookeeper.out的日志文件。除此之外,這個默認配置還沒有設置任何jvm相關的參數(所以堆大小是個默認值),這也是不可取的。那么有的同學說那我去修改Zookeeper的啟動腳本吧。最好不要這樣做,Zookeeper會加載conf文件夾下一個名為zookeeper-env.sh的腳本,所以你可以將一些定制化的配置寫在這里,而不是直接去修改Zookeeper自帶的腳本。
#!/usr/bin/env bash
JAVA_HOME= #java home
ZOO_LOG_DIR= #日志文件放置的路徑
ZOO_LOG4J_PROP="INFO,ROLLINGFILE" #設置日志輪轉
JVMFLAGS="jvm的一些設置,比如堆大小,開gc log等"
7. 地址
在實際環境中,我們可能因為各種原因比如機器過保,硬件故障等需要遷移Zookeeper集群,所以Zookeeper的地址是一個很頭痛的事情。這個地址有兩方面,第一個是提供給Client的地址,建議這個地址通過配置的方式下發,不要讓使用方直接使用,這一點我們前期做的不好。另外一個是集群配置里,集群之間需要通訊,也需要地址。我們的處理方式是設置hosts:
192.168.1.20 zk1
192.168.1.21 zk2
192.168.1.22 zk3
在配置里:
server.1=zk1:2081:3801
server.2=zk2:2801:3801
server.3=zk3:2801:3801
這樣在需要遷移的時候,我們停老的節點,起新的節點只需要修改hosts映射就可以了。比如現在server.3需要遷移,那我們在hosts里將zk3映射到新的ip地址。但是對于java有一個問題是,java默認會永久緩存DNS cache,即使你將zk3映射到別的ip,如果并不重啟server.1, server.2,它是不會解析到新的ip的,這個需要修改$JAVA_HOME/jre/lib/security/java.security文件里的networkaddress.cache.ttl=60,將其修改為一個比較小的數。
對于這個遷移的問題,我們還遇到一個比較尷尬的情況,在最后的坑里會有提及。
8. 日志位置
Zookeeper主要產生三種IO: txlog(每個寫操作,包括新Session都會記錄一條log),Snapshot以及運行的應用日志。一般建議將這三個IO分散到三個不同的盤上。不過我們倒是一直沒有這么實驗過,我們的Zookeeper也是運行在虛擬機(一般認為虛擬機IO較差)上。
9. 監控
我們對Zookeeper做了這樣一些監控:
a. 是否可寫。 就是一個定時任務定時的去創建節點,刪節點等操作。這里要注意的是Zookeeper是一個集群,我們監控的時候我還是希望對單個節點做監控,所以這些操作的時候不要連接整個集群,而是直接去連接單個節點。
b. 監控watcher數和連接數 特別是這兩個數據有較大波動的時候,可以發現使用方是否有誤用的情況
c. 網絡流量以及client ip 這個會記錄到監控系統里,這樣很快能發現『害群之馬』
10. 一些使用建議
a. 不要強依賴Zookeeper,也就是Zookeeper出現問題業務已然可以正常運行。Zookeeper是一個分布式的協調框架,主要做的事情就是分布式環境的一致性。這是一個非常苛刻的事情,所以它的穩定性受很多方面的影響。比如我們常常使用Zookeeper做服務發現,那么服務發現其實是不需要嚴格的一致性的,我們可以緩存server list,當Zookeeper出現問題的時候已然可以正常工作,在這方面etcd要做的更好一些,Zookeeper如果出現分區,少數派是不能提供任何服務的,讀都不可以,而etcd的少數派仍然可以提供讀服務,這在服務發現的時候還是不錯的。
b. 不要將很多東西塞到Zookeeper里,這個上面已經提到過。
c. 不要使用Zookeeper做細粒度鎖,比如很多業務在訂單這個粒度上使用Zookeeper做分布式鎖,這會頻繁的和Zookeeper交互,對Zookeeper壓力較大,而且一旦出現問題影響面廣。但是可以使用粗粒度的鎖(其實leader選舉也是一種鎖)。
d. 不建議做通用配置的第二個理由是,通用配置要提供給特別多特別多系統使用,而且一些公共配置甚至所有系統都會使用,一旦這樣的配置發生變更,Zookeeper會廣播給所有的watcher,然后所有Client都來拉取,瞬間造成非常大的網絡流量,引起所謂的『驚群』。而自己實現通用配置系統的時候,一般會對這種配置采取排隊或分批通知的方式。
11. 一些坑
a. zookeeper client 3.4.5 ping時間間隔算法有問題,在遇到網絡抖動等原因導致一次ping失敗后會斷開連接。3.4.6解決了這個問題 Bug1751。
b. zookeeper client如果因為網絡抖動斷開了連接,如果后來又重連上了,zookeeper client會自動的將之前訂閱的watcher等又全部訂閱一遍,而Zookeeper默認對單個數據包的大小有個1M的限制,這往往就會超限,最后導致一直不斷地的重試。這個問題在較新的版本得到了修復。Bug706
c. 拋出UnresolvedAddressException異常導致Zookeeper選舉線程退出,整個集群無法再選舉,處于崩潰的邊緣。這個問題是,某次OPS遷移機器,將老的機器回收了,所以老的機器的IP和機器名不復存在,最后拋出UnresolvedAddressException這個異常,而Zookeeper的選舉線程(QuorumCnxManager類里的Listener)只捕獲了IOException,導致該線程退出,該線程一旦退出只要現在的leader出現問題,需要重新選舉,則不會選出新的leader來,整個集群就會崩潰。Bug2319(PS,這個bug是我report的)