GitHub此前的搜索使用Solr實(shí)現(xiàn),新上線的搜索基于elasticsearch,運(yùn)行在多個(gè)集群上。由于代碼搜索索引很大,GitHub專門為其指定了一個(gè)集群。目前該集群包括26個(gè)存儲節(jié)點(diǎn)和8個(gè)客戶端節(jié)點(diǎn)。存儲節(jié)點(diǎn)負(fù)責(zé)保存構(gòu)成搜索索引的數(shù)據(jù),而客戶端節(jié)點(diǎn)負(fù)責(zé)協(xié)調(diào)查詢活動。每個(gè)搜索節(jié)點(diǎn)中有2TB的SSD存儲。
發(fā)生故障時(shí),整個(gè)集群保存了大概17TB的代碼。數(shù)據(jù)以分片方式跨越整個(gè)集群存儲,每個(gè)分片(shard)在另一個(gè)節(jié)點(diǎn)上有一份復(fù)制作為冗余,整個(gè)索引用去大約34TB存儲空間。整個(gè)存儲容量占用了集群總存儲空間的67%左右。代碼搜索集群運(yùn)行在Java 6和elasticsearch 0.19.9上。當(dāng)時(shí)這個(gè)集群已經(jīng)正常運(yùn)行了好幾個(gè)月。
在1月17日、星期四,準(zhǔn)備上線這個(gè)代碼搜索功能,以完成整個(gè)統(tǒng)一搜索的實(shí)現(xiàn)。后來發(fā)現(xiàn)elasticsearch已經(jīng)發(fā)布了0.20.2版本,其中包括多個(gè)功能修復(fù)和性能改進(jìn)。
他們決定推遲代碼搜索上線,先升級elasticsearch,希望這可以讓新功能的上線更加順暢。
在1月17日完成了升級,集群中所有節(jié)點(diǎn)都成功上線,而且恢復(fù)到正常的集群狀態(tài)。
問題就此開始出現(xiàn)。
從這次升級開始,集群中出現(xiàn)兩次故障。
elasticsearch與其他使用大量單一索引存儲數(shù)據(jù)的索引服務(wù)不同,它使用分片模式切分?jǐn)?shù)據(jù),這樣可以很容易地將數(shù)據(jù)分布在整個(gè)集群中,而且易于管理。每個(gè)分片自己是一個(gè)Lucene索引,elasticsearch使用Lucene合并索引來聚合所有的分片搜索查詢。
在升級后兩個(gè)小時(shí),第一次故障出現(xiàn)了,恢復(fù)的過程中要重啟整個(gè)集群。我們在索引日志中發(fā)現(xiàn)的錯誤信息指出:有些分片無法分配到特定節(jié)點(diǎn)上。進(jìn)一步檢查后,我們發(fā)現(xiàn):這些數(shù)據(jù)分片有些區(qū)段的緩存文件已經(jīng)損壞,其他在磁盤上已經(jīng)丟失,但elasticsearch仍然可以恢復(fù)有損壞區(qū)段緩存的分片,它也可以恢復(fù)丟失了一份復(fù)制的分片,但是總計(jì)510個(gè)分片中有7個(gè)分片,主分片和副本都已經(jīng)丟失了。
我們復(fù)核了發(fā)生故障的環(huán)境,當(dāng)時(shí)的結(jié)論是:我們發(fā)現(xiàn)的問題,來源于集群恢復(fù)帶來的高負(fù)載。對問題的進(jìn)一步研究后,沒有發(fā)現(xiàn)其他elasticsearch用戶遇到過類似問題。在那個(gè)周末,集群沒有問題,因此我們決定發(fā)布新功能。
下一次故障出現(xiàn)在1月24日、星期四。引起團(tuán)隊(duì)注意的,是他們的異常跟蹤和監(jiān)控系統(tǒng),其中檢測到大量的異常爆發(fā)。進(jìn)一步調(diào)查指出:大部分異常來自代碼查詢的超時(shí),以及加入新數(shù)據(jù)時(shí)更新代碼搜索索引的后臺作業(yè)。
這一次,我們開始同時(shí)檢查集群中全部節(jié)點(diǎn)的整體狀態(tài)和elasticsearch的日志。我們發(fā)現(xiàn):看似隨機(jī)性的多個(gè)存儲節(jié)點(diǎn)出現(xiàn)高負(fù)載。大多數(shù)節(jié)點(diǎn)的CPU使用率都是個(gè)位數(shù),有幾個(gè)消耗量幾乎100%可用的CPU核。我們可以消除系統(tǒng)和IO引起的負(fù)載,在這些服務(wù)器上唯一導(dǎo)致高負(fù)載的是運(yùn)行elasticsearch的Java進(jìn)程。現(xiàn)在搜索和索引還是會超時(shí),我們也在日志中注意到:一些節(jié)點(diǎn)被很快選為master,此后又很快被移除。為了消除這種快速切換master角色帶來的潛在問題,我們決定:最好的方法,是讓集群完全停機(jī),然后將其啟動,禁止數(shù)據(jù)分片的分配和重新平衡。
這種方式讓集群恢復(fù)上線,但是他們發(fā)現(xiàn)elasticsearch日志中有一些問題。集群重啟后,他們發(fā)現(xiàn)有些節(jié)點(diǎn)無法重新加入到集群中,有些數(shù)據(jù)分片試圖二次分配到同一個(gè)節(jié)點(diǎn)上。這次,他們求助于elasticsearch公司的技術(shù)人員,并確認(rèn):
這些無法分配的分片(主分片與復(fù)制合計(jì)23個(gè))都有數(shù)據(jù)丟失。除數(shù)據(jù)丟失外,集群花費(fèi)很多時(shí)間試圖恢復(fù)剩余的分片。在這次恢復(fù)過程中,我們必須多次重啟整個(gè)集群,因?yàn)槲覀兗尤肓硕啻紊壓团渲米兏?,必須要?yàn)證和再次恢復(fù)分片。這是這次故障中最耗時(shí)的部分,因?yàn)槎啻螐拇疟P中加載17TB索引數(shù)據(jù)十分緩慢。
和elasticsearch的技術(shù)人員一起,他們發(fā)現(xiàn)集群中有些地方配置錯誤,或是需要調(diào)優(yōu)配置,才能得到最佳性能。這次問題也讓elasticsearch的技術(shù)人員也發(fā)現(xiàn)了elasticsearch的兩個(gè)bug。還有一個(gè)很重要的原因:
我們當(dāng)時(shí)運(yùn)行的Java 6是2009年早期的版本,其中包含多個(gè)嚴(yán)重bug,影響elasticsearch和Lucene,同時(shí)造成大量內(nèi)存分配,導(dǎo)致高負(fù)載。
根據(jù)他們的建議,我們馬上升級了Java和elasticsearch,并按他們的推薦調(diào)整了配置。具體做法是:在我們的Puppetmaster上,針對這些特定變更,創(chuàng)建了一個(gè)新的話題分支,并在環(huán)境中的這些節(jié)點(diǎn)上運(yùn)行了Puppet。使用了新的配置、新版本elasticsearch和Java7之后,此前兩次故障中遇到的負(fù)載不穩(wěn)定和快速master選舉問題再也沒有出現(xiàn)了。
但是,1月28日,又出現(xiàn)一次故障。不過這次與之前沒有關(guān)系,完全是人為錯誤。
一名工程師要把包含有Java和elasticsearch更新的特性分支合并到我們的生產(chǎn)環(huán)境中。過程中,工程師將代碼搜索節(jié)點(diǎn)上的Puppet環(huán)境回滾到了生產(chǎn)環(huán)境中,這是在部署合并代碼之前。這導(dǎo)致elasticsearch在節(jié)點(diǎn)上重啟,因?yàn)镻uppet運(yùn)行在上面。
我們馬上就發(fā)現(xiàn)了問題根源,并停下集群,防止在一個(gè)集群中運(yùn)行多個(gè)版本Java和elasticsearch導(dǎo)致任何問題。當(dāng)合并代碼部署后,我們在所有代碼搜索節(jié)點(diǎn)上再次運(yùn)行Puppet,啟動集群。我們沒有選擇在集群處于降級狀態(tài)時(shí)建立索引和查詢,而是等它完全恢復(fù)。當(dāng)集群完成恢復(fù)后,我們將代碼搜索功能打開。
總結(jié)這幾次故障,Will指出:
在將代碼搜索集群中的elasticsearch升級到0.20.2版本之前,我們沒有在我們的基礎(chǔ)設(shè)施中對其進(jìn)行足夠測試,也沒在其他集群中測試。還有一個(gè)原因是:對于代碼搜索集群,我們沒有足夠的上線前(staging)環(huán)境。
現(xiàn)在運(yùn)行的Java版本已經(jīng)經(jīng)過elasticsearch團(tuán)隊(duì)測試,而且代碼集群配置也經(jīng)過他們審核,未來的審核過程也已經(jīng)安排確定。
對于周一的故障,我們正在開發(fā)自動化過程,保證這樣的效果:如果GitHub上的分支比Puppetmaster上分支更超前,確保這個(gè)環(huán)境中的Puppet不會運(yùn)行。
最后,elasticsearch團(tuán)隊(duì)提供了對運(yùn)行大集群的幾點(diǎn)優(yōu)化建議:
1.設(shè)置ES_HEAP_SIZE環(huán)境變量,保證JVM使用的最大和最小內(nèi)存用量相同。如果設(shè)置的最小和最大內(nèi)存不一樣,這意味著當(dāng)jvm需要額外的內(nèi)存時(shí)(最多達(dá)到最大內(nèi)存的大小),它會阻塞java進(jìn)程來分配內(nèi)存給它。結(jié)合使用舊版本的java情況就可以解釋為什么集群中的節(jié)點(diǎn)會停頓、出現(xiàn)高負(fù)載和不斷的進(jìn)行內(nèi)存分配的情況。elasticsearch團(tuán)隊(duì)建議給es設(shè)置50%的系統(tǒng)內(nèi)存
2.縮短recover_after_time超時(shí)配置,這樣恢復(fù)可以馬上進(jìn)行,而不是還要再等一段時(shí)間。
3.配置minimum_master_nodes,避免多個(gè)節(jié)點(diǎn)長期暫停時(shí),有些節(jié)點(diǎn)的子集合試圖自行組織集群,從而導(dǎo)致整個(gè)集群不穩(wěn)定。
4.在es初始恢復(fù)的時(shí)候,一些節(jié)點(diǎn)用完了磁盤空間。這個(gè)不知道是怎樣發(fā)生的,因?yàn)檎麄€(gè)集群只使用了總空間的67%,不過相信這是由于之前的高負(fù)載和舊java版本引起的。elasticsearch的團(tuán)隊(duì)也在跟進(jìn)這個(gè)問題。
原文地址