《英雄聯盟》支撐最高750萬同時在線用戶的聊天服務打造

摘要:回顧《英雄聯盟》的發展無疑是一個高速成長的光輝史,然而這個光輝史賴以生存的基礎設施卻不得不克服一次又一次的挑戰,歷經一次又一次的迭代,就比如本次我們要說的聊天服務。

【編者按】在2013年初馬化騰被問及“過去兩年騰訊在海外投資中最成功的案例是什麼”時,他毫無疑問的回答:“投資美國的Riot Games,做出《英雄聯盟》。”在那個時候,《英雄聯盟》這款遊戲僅上市3年,卻以500萬同時在線(日活躍用戶1200萬)玩家數量橫掃全球,成爲全世界第一大線上遊戲。而值得一提的是,一年後(2014年),該遊戲的日活躍玩家數量已超過2700萬,最高同時在線玩家也達到了750萬。

 

回顧《英雄聯盟》的發展無疑是一個高速成長的光輝史,然而這個光輝史賴以生存的基礎設施卻不得不克服一次又一次的挑戰,歷經一次又一次的迭代,就比如下面我們要說的聊天服務。近日,HighScalability創始人Todd Hoff總結了Riot Games公司Michal Ptaszek在 Strange Loop 2014會議上的演講“Scaling League of Legends Chat to 70 million Players”( PPT下載),簡述了該遊戲聊天服務“Make it work. Make it right. Make it fast.”理念的實現途徑。

以下爲譯文

 

如上圖所示,該遊戲的聊天服務需要支撐750萬併發用戶,2700萬日活躍用戶,每秒鐘需要處理的消息上萬條,每臺服務器每天處理消息達十億條。

對於對戰類型遊戲,團隊間交流直接影響到了比賽的勝負。爲了幫助完成這一目標,聊天服務初始就使用了XMPP特性,就如WhatsApp一樣。在小規模下實現並沒有什麼難度,可以說是開箱即用,然而當用戶快速增長時,挑戰也隨之而來。爲了更快和更好的實現這一點,WhatsApp和LOL(英雄聯盟)不得不定製化Erlang VM,對其進行優化並添加多個監控功能,只爲解決大規模下的性能瓶頸。

縱觀整個服務架構,Riak CRDTs(commutative replicated data types,可交換多副本數據類型)應用無異是最大的亮點,通過零可變貢獻實現大規模線性橫向擴展。時至今日,CRDTs的使用範圍仍然很小,甚至大多數人都沒聽說過這項技術,但是它將來必將成爲衆多機構的寵兒,在寫操作上另闢奇徑。下面我們一起看LOL如何打造支撐超過7千萬玩家的聊天系統:

狀態

  • 月6700萬的獨立訪問玩家,不包括其他使用這個系統的服務
  • 日活躍玩家2700萬
  • 750萬的併發玩家
  • 每臺服務器每天路由10億個事件,值得一提的是,CPU和內存使用率只有20-30%
  • 每秒處理1.1萬條消息
  • 世界範圍內部署的chat服務器達數百臺,負責運維人員只有3個
  • 99%的可用率

平臺

  • Ejabberd (Erlang based) XMPP server
  • Riak
  • Load Balancer
  • Graphite
  • Zabbix
  • Nagios
  • Jenkins
  • Confluence

Chat

1. 支持私聊和羣聊

2. Chat擁有獨立的界面,同時還支持好友列表。你可以查看好友的連接狀態(在線或離線)、遊戲狀態、遊戲時間,以及獲得過的獎項。

3. REST APIs讓chat可以作爲其他LoL服務的後端服務。舉個例子,store會與chat通信來驗證好友關係。Leagues會使用chat的社交圖譜將新玩家組織到一起。這樣一來,這些新玩家就可以交到一些志同道合的朋友,從而增加在線時間。

4. Chat必須穩定的保持在一個低延時環境,chat的宕機會拉低整個遊戲的用戶體驗。

5. 選擇XMPP作爲協議,提供消息、狀態信息並且負責通訊列表維護。

6. 基於性能和新功能等原因,他們不得不偏離核心XMPP協議。

7. Chat服務打造時就選擇了Ejabberd作爲服務器。Erlang同樣非常棒,擁有更好的錯誤隔離和可追溯性。同時,它還支持代碼的熱加載,如此一來,給bug打補丁時就不需要再重啓服務。

8. 目標是零共享以實現線性橫向擴展,同時零共享還更有益於錯誤隔離及追溯。在零共享實現上,系統剛還有一些提升空間。

9. 運維人員只有3人,這樣就對chat服務器容錯提出了非常高的要求,同時也意味着不是每個故障都需要人力介入。

10. 讓它崩潰。不要試圖從一個嚴重的故障中做緩慢的恢復。取而代之,從一個已知的狀態下重啓更加適合。舉個例子,當大量數據庫查詢積壓時,重啓可以讓新的查詢實時完成,隊列中的查詢則另選恰當時間進行。

11. 每臺服務器上都運行了Ejabberd和Riak,Riak作爲服務器使用。在需要時,可添加服務器對系統進行橫向擴展。Ejabberd和Riak運行在不同的集羣中。

12. Riak服務器使用了多數據中心備份機制,它們還會提供數據給第二Riak集羣。類似社交圖等昂貴的ETL查詢都運行在第二集羣上,從而避免主集羣受到影響。備份操作同樣會在第二集羣上進行。

13. 擴展性、性能和容錯機制是個長期奮鬥目標,大部分的Ejabberd代碼都已經被重寫。

  • 重寫以匹配自己的需求。舉個例子,LoL中只存在雙向好友關係,但是XMPP機制卻允許不一致的好友關係。也就說是,基於XMPP建立好友列表需求16條客戶端與服務器之間的消息(對於數據庫來說這是一個非常重的負載),而重寫後的協議完成這個操作只需要3條消息。
  • 移除不必要及不期望的代碼。
  • 優化協議的本身。
  • 編寫測試以避免崩潰。

14. 消除明顯的瓶頸。

15. 避免共享可能會變化的狀態,這樣可以更容易的進行大規模線性擴展。

  • Multi User Chat(MUC)。每個Chat服務器都可以支撐數百萬連接數。每個用戶連接中都包含了一個會話進程,當用戶期望修改狀態或者給一個房間發送消息時,事件則會被傳送到一個被稱爲MUC路由器的單進程,然後MUC會將消息傳遞給相關的羣聊。這是一個很明顯的瓶頸,解決的方法是併發路由。優化之後,羣聊房間的尋找會放在用戶會話中,從而利用所有的核心。
  • 每個Ejabberd服務器都包含了會話列表的一個副本,它是用戶ID和會話之間的映射。發送消息需要查找用戶會話在集羣中的位置,隨後消息會被寫入會話列表。通過校驗會話是否存在、優先級以及一些其他的查詢,寫入操作的數量可以降低96%。這些校驗對系統的提升非常大,可以大幅度減少用戶登陸操作及狀態修改時間。

16. 增加功能以獲得生產環境中代碼的能見度。讓代碼可以在涉及到同一事務的多個服務器上同時升級。

17. 優化Erlang VM中的服務器調試功能。獲得會話內存使用情況,以更好地進行內存使用優化。

18. 項目開始時就考慮到了數據庫擴展性。開始時選擇的MySQL造成了性能、可靠性、擴展性等多方面的問題。比如,數據模式的修改速度匹配不了代碼的變更。

  • 因此他們選擇了Riak。Riak是個分佈式的高容錯鍵值存儲。無主的機制讓它可以避免單點故障,即使兩臺服務器同時發生故障也不會影響服務或丟失數據。
  • 需要在chat服務器上投入大量的精力以實現最終一致。實現了一個Ejabberd CRDT庫處理所有的寫入衝突。嘗試將對象轉換到一個穩定的狀態。
  • CRDT是如何工作的?取代給好友列表直接添加一個新層,CRDT中爲對象維護了一個操作日誌,日誌中記錄的格式類似“Add Player 1”和“Add Player 2”。下一次對象會以請求的方式讀取日誌,從而解決了任何衝突。提供給對象的日誌是無序的,因爲這裏並不需要去關心順序。這樣操作保證了好友列表的一致性。這裏的理念是在合適時即對值進行修改,而不是爲對象建立一個冗長的操作日誌,並且只在對象的讀取時完成操作。
  • Riak是個非常大的成功,它提供了幾乎線性的擴展性,鑑於對象可以被非常快的修改還提供了不錯的模式靈活性。
  • 這是一個非常大的觀念變革,它改變了服務測試和工具建立的方式。

監視

  • Chat服務建立了500個以上的計數器,每分鐘都會對結果進行收集並傳送給監視系統(Graphite、Zabbix、Nagios)。
  • 爲計數器設定了閾值,在超過警戒線時會進行提醒。因此,在影響用戶體驗或者系統發生問題之前,問題就會被定位。
  • 舉個例子,最近有一次客戶端升級造成了無限廣播用戶狀態的問題。着眼Graphite,工程師很快定位到服務器因新客戶端上線(帶來的新特性)而崩潰。

實現Feature Toggles(功能表示)

  • 在無需重啓服務的情況下實現新和功能上線下線。
  • 新功能開發時就會被添加Toggles(on/off )特性,如果一個功能導致問題產生,工程師可以立刻關閉它。
  • 部分部署。新代碼可以只對某些特定的用戶開放,或者只是某些特定的用戶可以激活新代碼,這允許在某個範圍內測試風險較高的功能。一旦該功能通過測試,它就會被髮布到所有用戶。

動態代碼重載

  • Erlang的一大特性就是動態熱加載新的代碼。
  • 在第三方客戶端(比如 pidgin)並沒有經過良好的測試時,比如它會發送與官方客戶端不同類型的事件,補丁在無需重啓整個chat服務器時就可以快速被部署並集成到chat服務器,從而顯著的減少玩家宕機。

日誌

  • 記錄所有異常情況,比如錯誤和警報。
  • 服務器同樣提供了健康檢查報告,這樣就可以查看日誌(登陸用戶數量、接受新的連接數以及好友列表修改情況)並決定這個服務器是否運行良好。
  • 爲調試模式嵌入選擇用戶會話功能。如果存在可以或者測試用戶(生產服務器上的QA測試),即使chat服務器上存在10萬個會話,需要加載的會話也只有一個。日誌只包括XML流量、事件以及度量,這將節省大量的日誌空間。
  • 通過整合功能標識、部分部署和日誌選擇功能,系統已經完成了給部分用戶推送新功能的準備;同時,系統還可以在沒有其他用戶干擾的情況下收集和分析日誌。

加載測試代碼

  • 每天晚上,自動校驗系統都會在測試環境中部署所有改變,並進行一連串的負載測試。
  • 測試過程中,服務器健康狀態會被監控,度量會被取出並分析。系統會建立一個Confluence頁面來記錄所有度量和測試結果,測試結果概要會通過郵件發送。
  • 可以做功能發佈前後的對比,這樣很容易對比代碼改變產生的影響,也很容易定位造成災難或者提升內存消耗X個百分比的問題。

未來的工作

  • 棄用MySQL。
  • 將chat服務擴展到遊戲外,這樣玩家在不登陸游戲的情況下就可以與好友交互。
  • 通過社交圖來提升體驗。分析玩家關係,並找出影響遊戲興趣的原因。
  • 計劃將遊戲內chat遷移到遊戲外服務器。

學到的知識

1. 故障肯定會產生,你不需要做到完全控制。即使你的代碼不存在bug,也可能存在一個ISP路由器以及10萬個用戶同時丟失的情況。做好萬全之策。確保系統可以承擔同時丟失一半用戶的情況,或者在丟失1/4 chat服務器的情況下不會影響到性能。

2. 規模擴大小概率事件變常態。如果某個bug發生的概率是十億分之一,但是在LoL的規模,這個bug可能每天都會發生一次。即使某些完全不可能發生的事件都有可能發生。

3. 成功的關鍵是清楚系統發生的所有事情。必須清楚你係統是健康的或者瀕臨崩潰。

4. 指定一個策略。LoL爲其chat服務選擇了橫向擴展策略。爲了支撐這個策略,他們選擇了一個不同的途徑來支撐這個策略。他們不僅選擇了Riak這個NoSQL數據庫,同時還挑戰了CRDTs這個途徑,只爲了橫向擴展能儘可能的無縫和強大。

5. 可用。貫穿開始和衍變。他們開始於Ejabberd,這並不一定代表着Ejabberd更容易開始,但是Ejabberd絕對可以更匹配他們的需求。

6. 讓一切更可見。增加追蹤、警報、監視、同樣一級一切有意義的東西。

7. 讓系統可運維。LoL給軟件更新添加了事務特性,還給系統添加了功能標識、熱更新、自動化測試加載、高可配置日誌等級等功能,這一切都只是爲了更容易管理。

8. 減少無用協議。定製系統所需的功能。如果你的系統只存在雙向好友關係,那麼你不再需要那個通用的昂貴協議。

9. 避免可變狀態共享。這條已衆所周知,但是仍然有系統會共享可變狀態,從而導致橫向擴展時的各種問題。

10. 利用好你的社交圖。Chat服務提供了一個原生的社交圖。這些信息可以被用於提升用戶體驗,以及開發更有意思的新功能。

原文鏈接: How League Of Legends Scaled Chat To 70 Million Players - It Takes Lots Of Minions. (翻譯/童陽 責編/仲浩)

發佈了171 篇原創文章 · 獲贊 68 · 訪問量 229萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章