大綱:前言日誌系統架構是怎樣的遊戲分析有什麼內容為什麼要自己架一個系統FEN架構 架構圖 Fluentd ElasticSearch NodeJS pusher logger analyser 使用者介面總結前言
最近我司需要做一個統一的遊戲日誌系統,要求有一定的通用性,能應對公司所有的遊戲業務。接下來分享一下這次日誌系統的專案經驗。
日誌系統架構是怎樣的目前流行的日誌系統為ELK,由Beats、Logstash、Elasticsearch、Kibana等元件共同實現,但萬變不離其宗,一個基本的日誌系統架構類似如下:
遊戲分析有什麼內容遊戲分析,與其它服務系統不同的是,遊戲內的系統可能是天馬行空的,資料型別是多樣的,甚至頻繁變化的。我們要在變化中總結到不變的內容,例如系統經濟產出,玩家物品消耗,商店購買等進行分析。所以這次的遊戲日誌系統要滿足以下需求:
記錄遊戲日誌,並隨時檢索日誌;分析玩家行為:玩家留存相關,玩家物品消耗,商店消耗等有一定複雜度的分析;能建立一個統一的日誌系統:一次性滿足未來遊戲運營多樣性。為什麼要自己架一個系統雖然ELK在安裝配置方面不算困難,外掛眾多,例如Filebeat,讀log檔案,過濾格式,轉發,但誰來生產這些log檔案,沒有提及。實際上,業務具有多樣性,只要有日誌檔案的地方,它就可以用。例如多數會使用Nginx進行日誌收集。我們也需要考慮到日誌生產者的問題,責權分離,需要單獨一臺機子進行日誌採集。
遊戲是一種技術與藝術結合的產品,資料龐雜,形態各異,光日誌埋點也花不少功夫複雜,但不能因此放棄治療。好的遊戲日誌,還可以幫我們還原玩家玩家畫像。遊戲更新週期短,資料變化大,需要提供更實時參照報表,為非技術人員更好友的查詢介面,才能更好的服務於遊戲資料分析。ELK 在這方面,基本解決了採集和儲存的問題,但實現分析方面還不能滿足我們的需求。
經過一翻思索,我們可以用現有工具,粘合多個套件,所以,我們有了以下思路:
日誌採集器:利用Fluented作為日誌檔案採集器,生產者透過內網HTTP傳送到採集器上,那每個生產者同一內網只要部署一個採集器即可,如果量特別大,可以多個,遊戲的功能埋點可以統一;
接收器與實時分析:接收器可以用Koa實現,Redis進行快取;同時用NodeJS另外一個程序分析和日誌入庫,分析行為,玩家畫像,得出報表,這些非日誌源的資料,可以放到MongoDB上,因為這些資料是修改性增長緩慢資料,佔用空間不大;
儲存倉庫:ElasticSearch是個很好的選擇,能叢集,可熱增減節點,擴容,還可以全文檢索,分詞;
使用者介面:Kibana針對 ElasticSearch提供良好的分析,結合原有的管理後臺系統,我們自己實現了一套使用者介面。
FEN架構這個框架主要使用到了Fluentd,ElasticSearch,以及NodeJS,我就稱它為 FEN 架構吧,如下圖。
架構圖上圖看出,這樣的日誌架構和第一個圖基本沒什麼不同,只是多了後面的分析與分批入庫處理,並且大量使用了NodeJS。
注:在這裡不會介紹各元件的詳細的安裝配置方法,網上有太多了,怎樣使用好每一個元件才是關鍵。
先介紹我們用到的工具:
FluentdFluentd是一個完全開源免費的log資訊收集軟體,支援超過125個系統的log資訊收集。Fluentd在收集源日誌方面非常方便而且高效能,透過HTTP GET就可以,這類似於Nginx的日誌記錄行為。它的優點是,日誌檔案可以高度定製化,例如我們這裡每5秒生成一個檔案,這樣每分鐘有12個檔案,每個檔案體積非常小。為什麼要這樣做?下面會介紹。Fluentd還有非常多的外掛,例如直接存入MongoDB,亞馬遜雲等,要是熟悉Ruby,也可以自己寫外掛。
ElasticSearch有人使用MongoDB進行日誌收集,是非常不明智的,只有幾千萬條還可以,如果半個月生產10億條日誌呢?日誌檔案需要儲存一個月甚至更長,那麼叢集和硬碟維護就非常重要。使用便利性也很重要,例如分詞檢索,在客服回溯玩家日誌,分析遊戲 BUG 的時候非常有用。下文的 ES 也是該元件的簡稱。
NodeJSNodeJS不適合做 CPU 密集型任務,但在網路應用方面還不錯,並且是我們正好熟悉的。日誌系統並不對實時性要求並不高,延時半小時以內都是允許的,事實上,正常情況延時也就10來秒。下面的讀與轉發日誌的Pusher,收集日誌的logger,分析日誌並資料落袋為安的的analyser,都是由NodeJS實現的。
下面繼續介紹用 NodeJS實現的每一個部分:
轉發器Pusher上面說到,為什麼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:分析器讀取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的意味。作者:govo連結:https://juejin.cn/post/6908901145184796680來源:掘金