基於NodeJS的高性能分佈式遊戲日誌系統

大綱:

  • 前言
  • 日誌系統架構是怎樣的
  • 遊戲分析有什麼內容
  • 爲何要本身架一個系統
  • FEN架構
    • 架構圖
    • Fluentd
    • ElasticSearch
    • NodeJS
    • pusher
    • logger
    • analyser
    • 用戶界面
  • 總結

前言

最近我司須要作一個統一的遊戲日誌系統,要求有必定的通用性,能應對公司全部的遊戲業務。接下來分享一下此次日誌系統的項目經驗。node

日誌系統架構是怎樣的

目前流行的日誌系統爲ELK,由Beats、Logstash、Elasticsearch、Kibana等組件共同實現,但萬變不離其宗,一個基本的日誌系統架構相似以下:redis

基本架構

遊戲分析有什麼內容

遊戲分析,與其它服務系統不一樣的是,遊戲內的系統多是天馬行空的,數據類型是多樣的,甚至頻繁變化的。咱們要在變化中總結到不變的內容,例如系統經濟產出,玩家物品消耗,商店購買等進行分析。因此此次的遊戲日誌系統要知足如下需求:express

  • 記錄遊戲日誌,並隨時檢索日誌;
  • 分析玩家行爲:玩家留存相關,玩家物品消耗,商店消耗等有必定複雜度的分析;
  • 能創建一個統一的日誌系統:一次性知足將來遊戲運營多樣性。

爲何要本身架一個系統

雖然ELK在安裝配置方面不算困難,插件衆多,例如Filebeat,讀log文件,過濾格式,轉發,但誰來生產這些log文件,沒有說起。實際上,業務具備多樣性,只要有日誌文件的地方,它就能夠用。例如多數會使用Nginx進行日誌收集。咱們也須要考慮到日誌生產者的問題,責權分離,須要單獨一臺機子進行日誌採集。緩存

遊戲是一種技術與藝術結合的產品,數據龐雜,形態萬千,光日誌埋點也花很多功夫,但不能所以放棄治療。好的遊戲日誌,還能夠幫咱們還原玩家玩家畫像。遊戲更新週期短,數據變化大,須要提供更實時參照報表,爲非技術人員更好友的查詢界面,才能更好的服務於遊戲數據分析。ELK 在這方面,基本解決了採集和儲存的問題,但實現分析方面還不能知足咱們的需求。安全

通過一翻思索,咱們能夠用現有工具,粘合多個套件,因此,咱們有了如下思路:服務器

  • 日誌採集器:

利用Fluented做爲日誌文件採集器,生產者經過內網HTTP發送到採集器上,那每一個生產者同一內網只要部署一個採集器便可,若是量特別大,能夠多個,遊戲的功能埋點能夠統一;markdown

  • 轉發器:

利用NodeJS進行 HTTP 轉發便可,前提是能按順序和分段讀取日誌文件,結合Fluented間接實現;網絡

  • 接收器與實時分析:

接收器能夠用Koa實現,Redis進行緩存;同時用NodeJS另一個進程分析和日誌入庫,分析行爲,玩家畫像,得出報表,這些非日誌源的數據,能夠放到MongoDB上,由於這些數據是修改性增加緩慢數據,佔用空間不大;數據結構

  • 儲存倉庫:

ElasticSearch是個很好的選擇,能集羣,可熱增減節點,擴容,還能夠全文檢索,分詞;架構

  • 用戶界面:

Kibana針對 ElasticSearch提供良好的分析,結合原有的管理後臺系統,咱們本身實現了一套用戶界面。

FEN架構

這個框架主要使用到了Fluentd,ElasticSearch,以及NodeJS,我就稱它爲 FEN 架構吧,以下圖。

架構圖

FEN架構

上圖看出,這樣的日誌架構和第一個圖基本沒什麼不一樣,只是多了後面的分析與分批入庫處理,而且大量使用了NodeJS。

注:在這裏不會介紹各組件的詳細的安裝配置方法,網上有太多了,怎樣使用好每個組件纔是關鍵。

先介紹咱們用到的工具:

Fluentd

Fluentd是一個徹底開源免費的log信息收集軟件,支持超過125個系統的log信息收集。Fluentd在收集源日誌方面很是方便並且高性能,經過HTTP GET就能夠,這相似於Nginx的日誌記錄行爲。它的優勢是,日誌文件能夠高度定製化,例如咱們這裏每5秒生成一個文件,這樣每分鐘有12個文件,每一個文件體積很是小。爲何要這樣作?下面會介紹。Fluentd還有很是多的插件,例如直接存入MongoDB,亞馬遜雲等,要是熟悉Ruby,也能夠本身寫插件。

ElasticSearch

有人使用MongoDB進行日誌收集,是很是不明智的,只有幾千萬條還能夠,若是半個月生產10億條日誌呢?日誌文件須要保存一個月甚至更長,那麼集羣和硬盤維護就很是重要。使用便利性也很重要,例如分詞檢索,在客服回溯玩家日誌,分析遊戲 BUG 的時候很是有用。下文的 ES 也是該組件的簡稱。

NodeJS

NodeJS不適合作 CPU 密集型任務,但在網絡應用方面還不錯,而且是咱們正好熟悉的。日誌系統對實時性要求並不高,延時半小時之內都是容許的,事實上,正常狀況延時也就10來秒。下面的讀與轉發日誌的Pusher,收集日誌的logger,分析日誌並數據落袋爲安的的analyser,都是由NodeJS實現的。

下面繼續介紹用 NodeJS實現的每個部分:

轉發器Pusher

(注:這是一個nodeJS編寫的服務。) 上面說到,爲何Fluentd使用分割成多個小文件的方式,由於NodeJS在大文件處理方面並不友好,而且要考慮到經過網絡發送到另外一臺機,轉發速度比讀慢太多了,因此必須實現續傳與斷點記錄功能。想一想,若是讀幾百 M 的文件,出現中斷後,須要永久記錄上次位置,下次再今後處讀起,這就增長了程序複雜度。NodeJS雖然有readline模塊,但測過發現並不如文件流那樣可控,訪模塊用於交互界面尚可。相反,若是日誌分割成多個小文件,則讀的速度很是高效,而且每5秒一個文件,哪怕有上萬條記錄,文件也大不到哪裏去,內存也不會佔用太多,在斷點續傳與出錯重試方面都能自如應對。若是遊戲日誌增多,能夠增長節點來緩解文件過大的壓力。

爲何不直接讓日誌生產者直接發到Koa上?由於效率與帶寬。NodeJS的適合作網站,但比專業的HTTP服務器要弱太多,4核心主機面對3000QPS就吃力,更多的關於NodeJS的性能問題,能夠參考網絡文章。在高併發量下,帶寬是個很大的問題,尤爲是須要作統一服務,面對的狀況是日誌機器與遊戲並不在同一內網中。在10萬日活下,帶寬超過了50M,很是嚇人,帶寬但是很貴的,太高的帶寬費用在這裏性價比過低了。

Pusher的注意點:
  • 批量轉發:不要一條條日誌發,採用批量發送。根據單條日誌文件大小,若是是 JSON 數據,有10多個字段,那麼每次請求發送50~100條發送都是沒問題的,也就幾十 KB;
  • 串行序順發送:從時間小的文件,從文件關開始發,等待上一次發送請求完成再執行下一次;
  • 發送失敗保存重試:若是某一次請求失敗,則保存到另一個文件目錄,以時間戳做爲文件名,下次重試,儘量保證數據完整性;
  • 每100毫秒讀一次文件列表,檢查有沒有新的日誌文件。雖然是每5秒產生一第二天志文件,但有可能出現效率降低致使發送速度跟不上而產生文件積壓,即便是空讀也是容許的,這不基本不佔什麼CPU。第100毫秒的間隔不要使用setInterval,應該在上一次文件發送完畢再setTimeout來執行;
  • 發送速度提供可變性,若是下面的logger效率低下,上面的100毫秒能夠適當放緩一些。

日誌收集器logger

這裏咱們使用Koa做爲日誌採集器。使用Koa,不管在性能仍是開發效率上,都比expressJS高效。咱們還用到了Redis做爲緩存,而不是直接在這裏作分析任務,是爲了儘可能提升與Pusher的對接效率,畢竟日誌的生產速度是很快的,但網絡傳送是相對低效的。

logger的注意點:
  • 使用緩存緩存數據,如Redis;
  • 關注內存:logger與pusher是兩臺機子,當logger的緩存提高太快,也就是後面的分析與入庫速度跟不上了,須要返回消息告知pusher放慢發送速度;
  • 安全驗證:簡單的方式是pusher發送時能夠進行md5驗證,logger驗籤;
  • 若是使用Redis,在Redis 4.0如下,使用list記錄每條日誌 ID,日誌使用hash節省內存。在Redis 3.x不要使用Scan,它有BUG,就是Scan出的數量是沒法肯定的,就算明確指定了條數,但有可能出現一次讀數萬條,也有可能一次讀幾十條,這對後面的分析器很是不利;
  • Redis記得開啓 RDB,以及maxmemory設置,前者能夠在出問題時還原狀態,後者能夠防止出現災難時資源暴掉,搞崩其它服務;
  • 不管是否是使用Redis,應該使用支持管道,或者批量的方法,如redisio,根據機器效率,如每次滿500條就入緩存,不滿就100毫秒入一次,減小緩存操做次數能夠提升效率;
  • logger能夠用pm2的集羣模式,提升效率。

注:pm2 3.2.2的集羣可能出現集羣內端口衝突的弔詭問題,建議用3.0.3或最新版本

分析器analyser:

(注:這也是一個nodeJS編寫的服務。) 分析器讀取Redis的內容,這裏就是單進程的隊列操做。到這一步,日誌怎麼分析,就能夠很自由了。

分析器analyser的注意點:

  • 單線程能夠確保每一個玩家的日誌時間序列;
  • Redis的讀取使用管道,一次讀取數千條進行分析。參考值:目前每次讀3000條進行處理,在4核心中低配置雲主機下單線程佔用僅爲35%左右;
  • 日誌存ES:源日誌文件能夠進行進一步分析或者格式優化,處理後的放ES,ES 就是爲集羣而生,經過加入子節點能夠熱擴容,硬盤便宜,因此先作3個節點的集羣吧;
  • 配置好ES的索引(mapping),仔細考慮各字段類型,凡是要與搜索條件有關的,例如要查元寶大於多少的,那麼元寶字段必須有索引,不然將沒法根據該字段查找日誌。還有,想要分詞的必須使用text類型。日誌通常不會進行彙總,由於咱們已經統計大部份內容了,因此能夠適當減小doc_value,壓縮率等,不然一千萬條日誌半小時內就吃掉1G硬盤。這須要你好好研究 ES 的索引配置了,後面還得研究 ES 的搜索,由於它比MongoDB的複雜得多,但這很值得;
  • ES和MongoDB的入庫,使用批量處理,根據機器性能和系統資源找到合適的批處理數量。參考值,4核下 ES 批量入庫1000條效率300ms 左右;
  • ES 配置好內存,默認是1G JVM內存,常常不夠用就會崩潰。在配置文件同目錄下有個jvm option文件,能夠加大JVM,建議至少分配一半以上內存;
  • ES 的寫入效率:不要覺得 ES 的輸入速度很快,默認它是寫一條更新一條索引,也就是必須等把數據更新到索引纔會返回,不管使用批量處理仍是單個,日誌量大的時候,批處理僅100條也會超過500ms。設置durability爲async,不要立刻更新到索引;
  • ES使用別名索引,好處是當你須要重建索引時,能夠經過另外從新指向到新的索引,由於 ES 不能修改索引,只能重建;
  • 在分析的時候,先還原玩家畫像,對其它數據報表,組織好你的數據結構,數據量小、簡單的能夠同時放內存中進行計數,並按期條件清理,大的如玩家畫像放redis中,按期更新入庫。這些數據的緩存方式可使用完整版本,簡化問題,減小出現髒數據的可能;同時分析也要注意效率的問題,例若有Mongodb數據的讀寫,要務必配置到index,不然將引發災難性效率降低。

用戶界面

由於咱們自己有後臺管理系統,因此咱們很方便的把用戶畫像與其它分析點接了入去,在查詢玩家行爲時,咱們搜索ES,在查詢分析報表時,咱們查詢MongoDB中的數據。固然咱們也使用了Kibana來知足可能的需求。

總結

目前該日誌系統運行1個半月,由純MongoDB到結合 ES,走了很多彎路,還好如今終於穩定下來。目前在性能方面,logger 與 analyser都在同一臺機,平均 CPU 爲23%左右,高峯47%左右,說明還有更大的機器壓榨空間。

內存方面,在高峯期5G 之內,整體很是平穩沒多大波動,其中redis內存使用爲800MB之內,但機器是16G,還有很大餘量保障。

NodeJS 的腳本中,logger的CPU佔用更小,3條進程,每條才3%,每條內存佔用不到100MB。analyser 的 CPU 與內存佔用多一點,這一點能夠經過腳本內的參數調整,例如內存計數的內容清理得更快,使用pm2的話設置max_memory_restart : '4G' 均可以提升穩定性。

以上是我在遊戲日誌系統中的經驗總結。

新的挑戰

以上方式,是【本地文件收集】-【推送到】-【分析】-【入庫與分析結果保存】,萬一須要對已經入庫的數據從新分析呢?例如之前有個數據運營漏了放到需求裏,須要對某個時期的日誌從新分析。

解決思路有2個:

  • 運營人員導出日誌,從新分析。缺點是須要大量人工處理;
  • 把日誌分析模塊獨立出來,不要在入庫的時候分析,在入庫後分析,就是把原來的前分析改成後分析。後分析好處是模塊化,統計日誌的時間點可選,還能夠作到後臺界面裏隨時統計。有點kibana的意味。

參考文獻:

注:本文首次發佈與簡書

相關文章
相關標籤/搜索