【經(jīng)驗】Ceph對象存儲運維的驚魂72小時
Ceph作為一款開源的分布式存儲軟件,可以利用X86服務(wù)器自身的本地存儲資源,創(chuàng)建一個或多個存儲資源池,并基于資源池對用戶提供統(tǒng)一存儲服務(wù),包括塊存儲、對象存儲、文件存儲,滿足企業(yè)對存儲高可靠性、高性能、高可擴(kuò)展性方面的需求,并越來越受到企業(yè)的青睞。經(jīng)過大量的生產(chǎn)實踐證明,Ceph的設(shè)計理念先進(jìn),功能全面,使用靈活,富有彈性。不過,Ceph的這些優(yōu)點對企業(yè)來說也是一把雙刃劍,駕馭的好可以很好地服務(wù)于企業(yè),倘若功力不夠,沒有摸清Ceph的脾性,有時也會制造不小的麻煩,下面我要給大家分享的正是這樣的一個案例。
A公司通過部署Ceph對象存儲集群,對外提供云存儲服務(wù),提供SDK幫助客戶快速實現(xiàn)對圖片、視頻、apk安裝包等非結(jié)構(gòu)化數(shù)據(jù)的云化管理。在業(yè)務(wù)正式上線前,曾經(jīng)對Ceph做過充分的功能測試、異常測試和性能測試。
集群規(guī)模不是很大,使用的是社區(qū)0.80版本,總共有30臺服務(wù)器,每臺服務(wù)器配置32GB內(nèi)存,10塊4T的SATA盤和1塊160G的Intel S3700 SSD盤。300塊SATA盤組成一個數(shù)據(jù)資源池(缺省配置情況下就是名稱為.rgw.buckets的pool),存放對象數(shù)據(jù);30塊SSD盤組成一個元數(shù)據(jù)資源池(缺省配置情況下就是名稱為.rgw.buckets.index的pool),存放對象元數(shù)據(jù)。有過Ceph對象存儲運維部署經(jīng)驗的朋友都知道,這樣的配置也算是國際慣例,因為Ceph對象存儲支持多租戶,多個用戶在往同一個bucket(用戶的一個邏輯空間)中PUT對象的時候,會向bucket索引對象中寫入對象的元數(shù)據(jù),由于是共享同一個bucket索引對象,訪問時需要對這個索引對象加鎖,將bucket索引對象存放到高性能盤SSD組成的資源池中,減少每一次索引對象訪問的時間,提升IO性能,提高對象存儲的整體并發(fā)量。
系統(tǒng)上線后,客戶數(shù)據(jù)開始源源不斷地存入到Ceph對象存儲集群,在前面的三個月中,一切運行正常。期間也出現(xiàn)過SATA磁盤故障,依靠Ceph自身的故障檢測、修復(fù)機(jī)制輕松搞定,運維的兄弟感覺很輕松。進(jìn)入到5月份,運維兄弟偶爾抱怨說SSD盤對應(yīng)的OSD有時會變的很慢,導(dǎo)致業(yè)務(wù)卡頓,遇到這種情況他們簡單有效的辦法就是重新啟動這個OSD,又能恢復(fù)正常。大概這種現(xiàn)象零星發(fā)生過幾次,運維兄弟詢問是不是我們在SSD的使用上有什么不對。我們分析后覺得SSD盤的應(yīng)用沒有什么特別的地方,除了將磁盤調(diào)度算法修改成deadline,這個已經(jīng)修改過了,也就沒有太在意這個事情。
5月28日晚上21:30,運維兄弟手機(jī)上接到系統(tǒng)告警,少部分文件寫入失敗,馬上登陸系統(tǒng)檢查,發(fā)現(xiàn)是因為一臺服務(wù)器上的SSD盤對應(yīng)的OSD讀寫緩慢引起的。按照之前的經(jīng)驗,此類情況重啟OSD進(jìn)程后就能恢復(fù)正常,毫不猶豫地重新啟動該OSD進(jìn)程,等待系統(tǒng)恢復(fù)正常。但是這一次,SSD的OSD進(jìn)程啟動過程非常緩慢,并引發(fā)同臺服務(wù)器上的SATA盤OSD進(jìn)程卡頓,心跳丟失,一段時間后,又發(fā)現(xiàn)其它服務(wù)器上開始出現(xiàn)SSD盤OSD進(jìn)程卡頓緩慢。繼續(xù)重啟其它服務(wù)器上SSD盤對應(yīng)的OSD進(jìn)程,出現(xiàn)了類似情況,這樣反復(fù)多次重啟SSD盤OSD進(jìn)程后,起不來的SSD盤OSD進(jìn)程越來越多。運維兄弟立即將此情況反饋給技術(shù)研發(fā)部門,要求火速前往支援。
到辦公室后,根據(jù)運維兄弟的反饋,我們登上服務(wù)器,試著啟動幾個SSD盤對應(yīng)的OSD進(jìn)程,反復(fù)觀察比對進(jìn)程的啟動過程:
1、 用top命令發(fā)現(xiàn)這個OSD進(jìn)程啟動后就開始瘋狂分配內(nèi)存,高達(dá)20GB甚至有時達(dá)到30GB;有時因系統(tǒng)內(nèi)存耗盡,開始使用swap交換分區(qū);有時即使最后進(jìn)程被成功拉起,但OSD任然占用高達(dá)10GB的內(nèi)存。
2、 查看OSD的日志,發(fā)現(xiàn)進(jìn)入FileJournal::_open階段后就停止了日志輸出。經(jīng)過很長時間(30分鐘以上)后才輸出進(jìn)入load_pg階段;進(jìn)入load_pg階段后,再次經(jīng)歷漫長的等待,雖然load_pg完成,但進(jìn)程仍然自殺退出。
3、 在上述漫長啟動過程中,用pstack查看進(jìn)程調(diào)用棧信息,F(xiàn)ileJournal::_open階段看到的調(diào)用棧是在做OSD日志回放,事務(wù)處理中是執(zhí)行l(wèi)evelDB的記錄刪除;在load_pg階段看到的調(diào)用棧信息是在利用levelDB的日志修復(fù)levelDB文件。
4、 有時候一個SSD盤OSD進(jìn)程啟動成功,運行一段時間后會導(dǎo)致另外的SSD盤OSD進(jìn)程異常死掉。
從這些現(xiàn)象來看,都是跟levelDB有關(guān)系,內(nèi)存大量分配是不是跟這個有關(guān)系呢?進(jìn)一步查看levelDB相關(guān)的代碼后發(fā)現(xiàn),在一個事務(wù)處理中使用levelDB迭代器,迭代器訪問記錄過程中會不斷分配內(nèi)存,直到迭代器使用完才會釋放全部內(nèi)存。從這一點上看,如果迭代器訪問的記錄數(shù)非常大,就會在迭代過程中分配大量的內(nèi)存。根據(jù)這一點,我們查看bucket中的對象數(shù),發(fā)現(xiàn)有幾個bucket中的對象數(shù)量達(dá)到了2000萬、3000萬、5000萬,而且這幾個大的bucket索引對象存儲位置剛好就是出現(xiàn)問題的那幾個SSD盤OSD。內(nèi)存大量消耗的原因應(yīng)該是找到了,這是一個重大突破,此時已是30日21:00,這兩天已經(jīng)有用戶開始電話投訴,兄弟們都倍感“鴨梨山大”。已經(jīng)持續(xù)奮戰(zhàn)近48小時,兄弟們眼睛都紅腫了,必須停下來休息,否則會有兄弟倒在黎明前。
31日8:30,兄弟們再次投入戰(zhàn)斗。
還有一個問題,就是有些OSD在經(jīng)歷漫長啟動過程,最終在load_pg完成后仍然自殺退出。通過走讀ceph代碼,確認(rèn)是有些線程因長時間沒有被調(diào)度(可能是因levelDB的線程長時間占用了CPU導(dǎo)致)而超時自殺所致。在ceph的配置中有一個filestore_op_thread_suicide_timeout參數(shù),通過測試驗證,將這個參數(shù)設(shè)置成一個很大的值,可以避免這種自殺。又看到了一點點曙光,時鐘指向12:30。
有些進(jìn)程起來后,仍然會占用高達(dá)10GB的內(nèi)存,這個問題不解決,即使SSD盤OSD拉起來了,同臺服務(wù)器上的其它SATA盤OSD運行因內(nèi)存不足都要受到影響。兄弟們再接再厲啊,這是黎明前的黑暗,一定要挺過去。有人查資料,有人看代碼,終于在14:30從ceph資料文檔查到一個強(qiáng)制釋放內(nèi)存的命令:ceph tell osd.* heap release,可以在進(jìn)程啟動后執(zhí)行此命令釋放OSD進(jìn)程占用的過多內(nèi)存。大家都格外興奮,立即測試驗證,果然有效。
一個SSD盤OSD起來后運行一會導(dǎo)致其它SSD盤OSD進(jìn)程退出,綜合上面的分析定位,這主要是因為發(fā)生數(shù)據(jù)遷移,有數(shù)據(jù)遷出的OSD,在數(shù)據(jù)遷出后會刪除相關(guān)記錄信息,觸發(fā)levelDB刪除對象元數(shù)據(jù)記錄,一旦遇到一個超大的bucket索引對象,levelDB使用迭代器遍歷對象的元數(shù)據(jù)記錄,就會導(dǎo)致過度內(nèi)存消耗,從而導(dǎo)致服務(wù)器上的OSD進(jìn)程異常。
根據(jù)上述分析,經(jīng)過近2個小時的反復(fù)討論論證,我們制定了如下應(yīng)急措施:
1、 給集群設(shè)置noout標(biāo)志,不允許做PG遷移,因為一旦出現(xiàn)PG遷移,有PG遷出的OSD,就會在PG遷出后刪除PG中的對象數(shù)據(jù),觸發(fā)levelDB刪除對象元數(shù)據(jù)記錄,遇到PG中有一個超大的bucket索引對象就會因迭代器遍歷元數(shù)據(jù)記錄而消耗大量內(nèi)存。
2、 為了能救活SSD對應(yīng)的OSD,盡快恢復(fù)系統(tǒng),在啟動SSD對應(yīng)的OSD進(jìn)程時,附加啟動參數(shù)filestore_op_thread_suicide_timeout,設(shè)置一個很大的值。由于故障OSD拉起時,LevelDB的一系列處理會搶占CPU,導(dǎo)致線程調(diào)度阻塞,在Ceph中有線程死鎖檢測機(jī)制,超過這個參數(shù)配置的時間線程仍然沒有被調(diào)度,就判定為線程死鎖。為了避免因線程死鎖導(dǎo)致將進(jìn)程自殺,需要設(shè)置這個參數(shù)。
3、 在目前內(nèi)存有限的情況下,異常的OSD啟動會使用swap交換分區(qū),為了加快OSD進(jìn)程啟動,將swap分區(qū)調(diào)整到SSD盤上。
4、 啟動一個定時任務(wù),定時執(zhí)行命令ceph tell osd.* heap release,強(qiáng)制釋放OSD占用的內(nèi)存。
5、 SSD對應(yīng)的OSD出現(xiàn)問題的時候,按如下步驟處理:
a) 先將該服務(wù)器上的所有OSD進(jìn)程都停掉,以騰出全部內(nèi)存。
b) 然后啟動OSD進(jìn)程,并攜帶filestore_op_thread_suicide_timeout參數(shù),給一個很大的值,如72000。
c) 觀察OSD的啟動過程,一旦load_pgs執(zhí)行完畢,可以立即手動執(zhí)行ceph tell osd.N heap release命令,將其占用的內(nèi)存強(qiáng)制釋放。
d) 觀察集群狀態(tài),當(dāng)所有PG的狀態(tài)都恢復(fù)正常時,再將其他SATA盤對應(yīng)的OSD進(jìn)程啟動起來。
按照上述步驟,我們從17:30開始逐個恢復(fù)OSD進(jìn)程,在恢復(fù)過程中,那幾個超大bucket索引對象在做backfilling的時候需要較長時間,在此期間訪問這個bucket的請求都被阻塞,導(dǎo)致應(yīng)用業(yè)務(wù)請求出現(xiàn)超時,這也是單bucket存儲大量對象帶來的負(fù)面影響。
5月31日23:00,終于恢復(fù)了全部OSD進(jìn)程,從故障到系統(tǒng)全部成功恢復(fù),我們驚心動魄奮戰(zhàn)了72小時,大家相視而笑,興奮過度,再接再厲,一起討論制定徹底解決此問題的方案:
1、 擴(kuò)大服務(wù)器內(nèi)存到64GB。
2、 對新建bucket,限制存儲對象的最大數(shù)量。
3、 Ceph 0.94版本經(jīng)過充分測試后,升級到0.94版本,解決單bucket索引對象過大問題。
4、 優(yōu)化Ceph對levelDB迭代器的使用,在一個大的事務(wù)中,通過分段迭代,一個迭代器在完成一定數(shù)量的記錄遍歷后,記錄其當(dāng)前迭代位置,將其釋放,再重新創(chuàng)建一個新的迭代器,從上次迭代的位置開始繼續(xù)遍歷,如此可以控制迭代器的內(nèi)存使用量。
前事不忘后事之師,汲取經(jīng)驗教訓(xùn),我們總結(jié)如下幾點:
1、 系統(tǒng)上線前必須經(jīng)過充分的測試
A公司的系統(tǒng)上線前,雖然對ceph做了充分的功能、性能、異常測試,但卻沒有大量數(shù)據(jù)的壓力測試,如果之前單bucket灌入了幾千萬對象測試,也許就能提前發(fā)現(xiàn)這個隱患。
2、 運維過程中的每一個異常都要及時引起重視
此案例中,在問題爆發(fā)前一段時間,運維部門已經(jīng)有反饋SSD異常的問題,可惜沒有引起我們重視,倘若當(dāng)時就深入分析,也許可以找到問題根由,提前制定規(guī)避措施。
3、 摸清ceph的脾性
任何軟件產(chǎn)品都有相應(yīng)的規(guī)格限制,ceph也不例外。如果能提前深入了解ceph架構(gòu)及其實現(xiàn)原理,了解單bucket過度存放大量對象所帶來的負(fù)面影響,提前規(guī)劃,也不會出現(xiàn)本案例中遇到的問題。RGW對配額的支持非常全面,包括用戶級別的、bucket級別的,都可以配置單個bucket允許存放的最大對象數(shù)量。
4、 時刻跟蹤社區(qū)最新進(jìn)展
在Ceph的0.94版本中,已經(jīng)支持了bucket索引對象的shard功能,一個bucket索引對象可以分成多個shard對象存儲,可有效緩解單bucket索引對象過大問題。