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