1. 程式人生 > >[2015.6.6]LeanCloud 多項服務發生中斷,持續4小時

[2015.6.6]LeanCloud 多項服務發生中斷,持續4小時

以下是LeanCloud Cofounder/CEO 江巨集在LeanCloud部落格對整個事件的說明:

各位 LeanCloud 的使用者,大家好。

LeanCloud 的多項服務在六月六日週六下午發生了大約四個小時的中斷或不穩定。其中 16:10 到 19:09 為故障階段;19:09 到 20:17 為限流恢復階段。

在故障階段受到重大影響的服務包括:資料儲存、網站及控制檯、雲程式碼、推送、工單系統、使用者反饋、第三方登入、應用內社交;受到輕度影響的服務包括:簡訊、實時通訊服務中獲取聊天記錄的 API;未受影響的服務包括:統計分析、離線資料分析、應用內搜尋、文件。

在限流恢復階段受到重大影響的服務包括資料儲存、網站及控制檯、雲程式碼、推送、簡訊、工單系統、使用者反饋、第三方登入、應用內社交、統計分析、離線資料分析、應用內搜尋、文件、實時通訊中獲取聊天記錄的 API。實時通訊在這個階段未受影響。

在這次事故中沒有發生伺服器端資料的丟失或損壞。

我們知道這次服務中斷給很多使用者造成了實質性的影響。我寫這封信的目的是為了向用戶說明發生的事情,以及我們將如何改進產品和服務以降低類似事件發生的可能性。

架構總覽

為了更好地解釋這次事故,我想先簡單介紹一下 LeanCloud 的後端架構。以下是一個簡化版的架構圖。

leancloud架構

雖然實際上系統間的關聯比這個圖要複雜,並且有一些做非同步處理的系統沒有畫出來,但用以說明問題是足夠的。

在 LeanCloud 的最前端,除了負載均衡之外就是 API 服務叢集,也就是實現 RESTful API 的部分。客戶端應用通過 SDK 或直接呼叫 API 和它通訊。根據請求的型別,API 服務再呼叫後端的各個系統完成所需的功能。比如如果是統計服務的請求,事件資訊會被髮送到統計服務並最終被儲存到 HBase 叢集;如果是資料儲存服務的請求,那麼 API 服務就會呼叫 MySQL 和 MongoDB 叢集完成資料訪問。因為 MongoDB 儲存了每個應用的大部分資料,所以不少服務都需要從這裡獲取一些資訊,是整個系統中很重要的元件。

MongoDB 叢集儲存了所有應用除了統計和備份資料以外的其他資料。由於資料量巨大,所以我們對 MongoDB 進行了分片(sharding),讓資料分佈在不同 shard。每個 shard 又由 5 臺伺服器(節點)組成,其中每臺都儲存著這個 shard 的完整資料,這樣不但可以承擔更高的負載,也保證了即使其中一些伺服器壞掉,資料也是安全的。每個 shard 有一臺稱為 primary 的伺服器,所有對資料的修改都會首先在這臺伺服器進行,然後再複製到其他伺服器,而讀取可以從任何一臺伺服器進行。當 primary 出現問題時,會有一個自動選舉過程從其他成員裡選出新的 primary,讓服務可以繼續,這是實現容錯的方式。

故障說明

在故障發生前的一週,我們做了與本次故障相關的兩個改動。

為了更好地管理 LeanCloud 的大量伺服器,我們在近期引入了 Apache Mesos,這是一個為伺服器叢集的資源管理提供高層抽象介面的系統。Mesos 會在每臺受管理的伺服器上執行一個 mesos-slave程序。

我們把 MongoDB 從 2.4 升級到了 2.6。新版本對地理位置的查詢,以及 count 查詢的準確性有所改進,所以我們認為這次升級對使用者來說是值得的。但新版本的一個負面影響是,因為每次啟動時要檢查索引,所以啟動時間大大延長了。而如果 MongoDB 是在出錯的情況下重新啟動,會導致大量的索引被重新建立,進一步延長啟動的時間。

大約在 16:10 我們的運維工程師收到 MongoDB 叢集記憶體不足的報警,在初步診斷後,確定原因為mesos-slave 啟動的一個子程序佔用記憶體太多,有可能是存在記憶體洩漏,所以我們開始終止各臺伺服器的 mesos-slave 程序。在這時我們發現 MongoDB 的其中一個 shard 中有三個節點因為可用記憶體不足進入異常狀態,其中一個是 primary,所以我們開始重啟其中兩個節點。由於 MongoDB 2.6 在啟動過程中需要校驗資料並修復索引,所以這個過程很慢,而當時正是流量高峰期,這個 shard 剩下的節點不足以承擔當時的負載,所以很快被壓垮。由於 primary 的連線數被佔滿,這導致出問題的兩個節點無法加入叢集。在這種情況下,我們決定遮蔽所有 API 服務請求,並重啟處於錯誤狀態的三個節點。

在此之後,這三個節點分別經歷了多次重啟,原因是出問題的 shard 大約有 10,000 個數據庫,而新版 MongoDB 要驗證每個資料庫並修復索引。這不但使得重啟很慢,而且因為資料庫數量太多,這個過程會耗盡系統對子程序數的限制,所以在重啟之後叢集無法恢復正常可用狀態。直到我們找到原因並調整了系統設定,各個節點才完成正常啟動,叢集恢復可用。

在這個過程中,除了事故本身,我們在溝通上也犯了一些錯誤。當用戶詢問服務恢復時間時,我們給出了過於樂觀的估計,但因為以上所說的原因,多個 MongoDB 節點經過了多次重啟,實際恢復服務的時間晚了很多,這給使用者造成了進一步的困擾。

改進措施

  • 更新 MongoDB 節點記憶體報警值,預留更多響應時間。(已完成)
  • 在 MongoDB 節點暫停使用 mesos-slave。(已完成)
  • 設定各服務 OOM 優先順序,意外情況發生導致剩餘記憶體不夠時重啟低優先順序服務以保護高優先順序服務。(已完成)
  • MongoDB 叢集增加硬體資源。
  • 拆分 MongoDB 叢集為多個小叢集,降低單個叢集裡的資料庫數量。這也讓我們可以對 MongoDB 進行滾動升級,降低風險。
  • 在重要系統升級前進行更多事故恢復的測試,並以滾動方式在較長時間內逐步完成升級。
  • 降低 MongoDB 啟動時間。
  • 進行階段性的 MongoDB 壓測及故障恢復模擬。
  • status.leancloud.cn 從主站拆分,讓使用者得到更準確的故障資訊。
  • 改進 API 服務執行緒池分配機制,避免因為一個 MongoDB shard 異常而堵塞執行緒池。
  • 改進 API 服務流量控制,緊急情況時做到可以僅切斷對應特定 MongoDB shard 的請求。
  • 在發生線上事故時,我們將在 Blog 上開通直播貼,實時向用戶通報進展。

結語

我們肩上承載著為數萬開發者提供穩定服務的責任。週六傍晚事故持續的四個小時也是所有 LeanCloud 的同事最難熬的一段時間。我們瞭解到有的創業團隊為了當天進行的一個活動,在過去的時間裡非常辛苦地工作,而活動卻受到了 LeanCloud 事故的影響。這樣的事情讓同為創業公司的我們非常地難受和慚愧。

我們希望通過這個詳細的報告讓使用者全面地瞭解整個過程,並將盡一切努力降低未來發生類似事件的可能性。雖然任何人都沒有辦法完全保證服務中斷不會發生,但我們會採取一系列措施避免可預見的問題,並確保在發生意外的時候能更快地恢復。為了保持改進過程的透明,我們開放了一個 追蹤各項具體措施的 Trello board,使用者可以通過它隨時瞭解我們的執行過程。