覆蓋全球數十個國家,千億級的監(jiān)控體系是這樣煉成的
說明:本文為網易游戲監(jiān)控團隊負責人王維棟老師在 GOPS 2019 · 上海站的分享整理而成。
作者簡介
王維棟,網易游戲監(jiān)控團隊負責人
我跟大家介紹一下我們是如何在游戲領域做到千億級的監(jiān)控體系,還有我們在智能監(jiān)控方面的一些探索。
我是網易游戲監(jiān)控團隊的負責人,7年時間一直在做運維平臺相關的開發(fā)工作,擅⻓的領域是智能監(jiān)控以及應用性能調優(yōu)。
我會分為四個章節(jié)說。
首先就是游戲領域的監(jiān)控會有什么區(qū)別,全球布局游戲監(jiān)控又有什么樣的挑戰(zhàn)?面對海量的時間序列時我們是如何處理;第三部分說說在可視化和報警方面做的比較有亮點的地方,最后講一講我們在智能監(jiān)控方面的實踐。
1. 來自全球布局的游戲的監(jiān)控挑戰(zhàn)
首先說說傳統(tǒng)游戲架構是什么樣的,以前游戲架構大多都是單體架構,單服單機器。另外基礎設施比較單一,之前基本都是物理機。
另外,以前基本就是瞄準國內市場做事情。最后,監(jiān)控的層次也非常簡單,無非就是硬件、網絡、操作系統(tǒng)、進程和業(yè)務指標。
但現階段,我們面臨的監(jiān)控場景變化的太快了,游戲的架構多樣化,混合基礎設施在我們公司逐漸出現。此外,公司開始立足海外,在海外有非常好的增⻓。最后,傳統(tǒng)監(jiān)控也逐漸向可觀測性去擴展。
首先是游戲架構的變遷,從最開始的單機架構,擴展到分布式架構。也就是說,玩家看到一個游戲服,在后面會有十幾臺機器,有的甚至多達百臺機器,取決于玩法不同。
后來,很多游戲的開發(fā)接觸到微服務的概念,開始逐漸的把游戲里面比如大廳、聊天服務從游戲的核心邏輯里面獨立出來,變成微服務,對游戲服務提供支持。這種情況下,微服務場景開始逐漸在游戲場景里面出現。
第二方面,我們一直在做游戲上云,一開始在物理機器部署游戲服,后來做私有云,在虛擬機上部署。在出海的過程中也開始逐漸采購海外的公有云和第三方 IDC 的機器。
再后來我們開始做容器化,在容器化進行到一定程度,現在有一些游戲也開始嘗試云原生。但是我們這個過程不是一蹴而就的,畢竟體量比較大,而且一個公司可能會有幾百個游戲,在這種場景下就會出現一個混合云的狀態(tài),有些游戲還是物理部署的,有些游戲已經云原生了,這種情況下挑戰(zhàn)是非常大的。
另一方面,我們公司目前的游戲業(yè)務已經覆蓋到全球數十個國家,監(jiān)控也會有二三十個region 去覆蓋到全球的游戲服務;此外,我們會在海外采購多個云服務商,這種情況下監(jiān)控的挑戰(zhàn)也會增加。
從傳統(tǒng)監(jiān)控向可觀測性過渡的過程中,我們不僅有報警、可視化,還有 debugging、profiling。盡力讓系統(tǒng)更加透明、可視化,從而形成更好的理解來優(yōu)化我們的產品,做到更好的度量,形成一個良性閉環(huán)。
基于上面這些思路,我們目前的游戲監(jiān)控架構是這樣的,從下到上是監(jiān)控數據從產生到處理再到消費的過程,然后右邊會有一些控制層的東⻄,這張圖只畫出了最關鍵的一些點。
我們在采集層面會做很多數據入口,比如說有 SDK,agent,日志指標,還有第三方數據庫。通過多區(qū)域部署的就近接入層,把這些數據接過來導到中央,中央會用一個 kafka 的數據隊列做解耦和路由,支持多系統(tǒng)的數據訂閱,此外還有聚合,數據存儲。
在數據應用的迭代過程中,我們會有一些歷史包袱,一開始是看業(yè)務場景來做監(jiān)控的,所以就會出現一堆數據子系統(tǒng),比如說有客戶端監(jiān)控,用戶體驗的監(jiān)控,有服務端監(jiān)控,有資源相關監(jiān)控,有網絡監(jiān)控,還有性能優(yōu)化的監(jiān)控。
我們目前正在逐一整合,并且對外提供統(tǒng)一入口。報警層面是基于一個標準化的規(guī)則引擎做報警,現階段我們也在逐漸把異常檢測,事件關聯這些功能加進去,此外還有像問題生命周期管理、事件升級來確保通知可達等機制。
最上層我們提供了一些數據可視化、報警通知、實時分析、性能優(yōu)化等一系列的能力。控制層面,我們通過跟 CMDB 深度結合,訂閱 CMDB 變更來減少監(jiān)控的配置成本。
通過區(qū)域管理來做到全球化的監(jiān)控。我們的 agent 能夠支持到豐富的插件自定義功能,所以有一個插件倉庫。
最后我們做了一個命令管道,其實就是類似于像 Ansible 的東⻄,跟我們的 agent 集成,最直接的價值就是配置的分發(fā)、故障自愈等功能都可以依托這樣的基礎架構來構建。
2. 海量時間序列數據處理
接下來說說我們面對海量時間序列的時候做的一些事情。首先就是面對海量而且異構的監(jiān)控場景,我們做了監(jiān)控對象的抽象,所有的監(jiān)控概念都能夠自定義地套進去抽象的數據模型。
通過與 CMDB 的結合,我們做到比較小的管理成本。采集方面,我們給出多種采集方式去適配不同的業(yè)務場景,然后做了統(tǒng)一的入口,在統(tǒng)一的數據總線做了數據的對⻬、預處理等工作。
最后我們做了一個大規(guī)模的海量時間序列存儲。
首先來說監(jiān)控對象抽象,為什么要做這件事情呢?常規(guī)場景中,我們會監(jiān)控一個物理機、虛擬機、容器,再到一個進程。對于CPU、網卡等硬件,只要能標注它,就能把數據關聯上去。
隨著業(yè)務不斷的擴展,在游戲場景里面要監(jiān)控某個游戲場景, 要監(jiān)控某一次 battle,某一個 NPC 的屬性,游戲進程之間 RPC 的情況。這種場景下如果我們寫死配置,適配一個個場景,對于監(jiān)控人員來說維護成本很高。
所以我們做了一層抽象,跟業(yè)界比較流行的方案例如 OpenTSDB、Prometheus有共同之處。
我們把監(jiān)控對象抽象為 Entity,用 EntityType 描述它是屬于什么類型的,用 tags 描述它的屬性,同時 tags 也會有一個類似交叉表的用途,把 entity 關聯在一起。目前我們大概有 100 多個 EntityTypes,差不多500萬的 entity,4億級別的 timeseries。
剛才講到全球就近接入層,這是服務端監(jiān)控的基礎架構。最中間的紅色是一個 Arbiter,它的⻆色是仲裁,負責訂閱 CMDB 的變更,生成監(jiān)控配置,監(jiān)控配置就會被分發(fā)到每一個 region 里面,當 agent 入網的時候會先去詢問 Arbiter,我是屬于哪個region的?
然后 Arbiter 會告訴它所屬的 region 和 node 列表,agent 嘗試連接,成功就會入網,它會跟 node 保持一個⻓鏈接,把它產生的數據全部交給 Node 去中轉。node 到中央我們會做網絡優(yōu)化,比 agent 直接連到中央會快很多。
另一方面,保持⻓連接有助于配置同步。配置變更時,Arbiter會通過Node和⻓鏈接,實時推送配置下去到 Agent。
這一套架構有高可用的保障,首先 Arbiter 是單點,我們做了 Arbiters Active-Standby的模式,出問題的時候 Standby Arbiter 會接管主 Arbiters 的工作。
Arbiter 的邏輯基本都是冪等的,所以不會擔心數據和集群一致性的問題。Node 會和 Arbiter 保持心跳,如果 Node 失聯了,Arbiter 會把相關的 Agents 調度到其他的節(jié)點上去。此外,每個 region 里面會有多個 node 冗余,在 node 之間分配 agent 時,我們用了一致性哈希,去確保增刪 node 時,盡量減少 agent 分配關系的抖動。
這套流程,配置管理部分和數據流部分是分離的,上層Arbiter和node完全失聯的情況下,agent會跟node一直保持連接,直到接到新的配置。這種情況下,即使我們中央出現了故障,agent仍然會上報數據。
說一下我們的 region 是怎么劃分的,首先我們會有一批機器和它們的IP,CMDB 會用 IDC、ISP 等一系列信息,把IP分類,分成 CMDB 的 region。
當一個新的 CMDB region 產生的時候,我們會拿到變更事件,根據地區(qū)、ISP 等幾種條件判斷可能跟哪個 monitor region 比較近,或者相關的網絡質量比較好,然后在幾個候選 regions 中選一些點,跟新的 region 發(fā)起互相探測的任務,得到 rtt and loss。
管理員可以看到 一個包含這些數據的可供決策的列表,只要去選一個 monitor region,新的cmdb region 就會自動加入到 monitor region。
最小管理成本則是通過訂閱 CMDBS 的變更實現的,我們的 SRE 通過各種管理系統(tǒng)和 CMDB,來管理一些資源和業(yè)務的對應關系。
Arbiter 對需要生成配置的數據,做一個內存的 ORM,同時在 CMDB 接一個 db trigger,把它所有增刪改的事件打到 MQ, 在 arbiter 訂閱,實時更新 ORM。
然后基于這個 ORM 做配置生成,這些配置生成完之后就會推到 region nodes,然后推到 agent 去,最初這個架構做完之后,我們可以實現秒級的配置更新,但是后續(xù)規(guī)模變的非常龐大,維持秒級配置更新使用的資源不劃算,所以后續(xù)在這里面加了窗口,十秒或者一分鐘內變更的事件,我們會統(tǒng)一分 發(fā),盡量減少配置的抖動。
在數據采集方面,因為游戲面臨的場景非常多,所以我們提供了多個采集方式。
首先就是 agent 插件,它通過服務綁定到機器,在機器上采集數據然后推出去。主動監(jiān)控的 checker 插件也是類似的方案,我們提供各個 region 的探測點,做從外部發(fā)起的主動探測。此外也有一套框架,能讓用戶自己配置,從任意一臺主機上發(fā)起探測。
主動監(jiān)控支持 icmp/tcp/http 等一系列協(xié)議。此外,SRE 可以直接開發(fā)插件,覆蓋到業(yè)務協(xié)議。
第三個方式是 pusher,我們提供一套 SDK 和 server 端,主要場景是由進程內向外 push 數據。游戲開發(fā)只要把 SDK 引入到自己的代碼,調用幾個接口,然后就可以 push 數據。目標就是能夠實現任意環(huán)境下的數據push接口。
最后,游戲經常使用日志暴露一些統(tǒng)計指標,這種情況下我們也做了 log metric 的兼容,從實時日志流里過濾一些標準字段,把它直接導到監(jiān)控數據流。
此外,公司也在嘗試云原生的方案,所以我們也引入了 prometheus,我們集成了它的服務發(fā)現模塊,然后直接對接 prometheus exporter 的協(xié)議,用一組分布式的 agent 去拉exporter 暴露出的數據,這樣 k8s、etcd 等系統(tǒng)的監(jiān)控就可以直接對接。
對于容器的監(jiān)控,我們用插件封裝了 cadvisor。為了兼容公司內其他的時間序列數據,我們提供了第三方 db 的 adapter,用來從其他的DB里面導數據。
有了這些數據采集方式,我們就可以比較從容應對混合云下的監(jiān)控場景。
對于物理機和虛擬機,跑 agent,SRE 按需編寫和綁定插件;對于容器,我們比較傾向于用 pusher 或日志導出數據;對于比較固定的場景也支持從宿主直接 attach 到容器采集數據;云原生的場景使用 Cadvisor、Prometheus Exporter、日志指標等。
剛剛提到我們 agent 是插件化的框架,目前我們是用 Python 做這套 agent 的,所以對于 SRE 來說上手成本非常低。
SRE 想要開發(fā)一個插件,就可以在系統(tǒng)點一下,得到一個 Gitlab 的 repo,在代碼框架里面填采集數據的代碼,push 上去。
只要 push 到保護分支,就會自動打成一個pip包,然后丟到 pip 源上面去。接下來只需要服務綁定 插件,這些服務的機器就會get到這個配置,然后 agent 就會裝這個插件去跑。
現階段我們已經800多個Python插件,覆蓋了絕大多數的業(yè)務場景。包括前面講到 的多點探測、故障自愈相關功能都是通過插件支持的。
當數據收集上來之后,我們有一個統(tǒng)一總線做處理。首先所有數據都會進到一個 Kafka Origin Data Topics 里,然后 PreProcessor 做一些數據清洗,過濾非法數據。做數據對⻬,然后進入 MainFlow。
進到 MainFlow ,我們會有一個 Flink Aggregator 去負責做聚合,用戶會在系統(tǒng)上配 一些可視化和報警規(guī)則,按這些規(guī)則延生成一些統(tǒng)一規(guī)范下的聚合規(guī)則,Flink 規(guī)則聚合數據,再把聚合好的數據丟回 MainFlow。
后面會有幾個系統(tǒng)去訂閱,首先是存儲,然后周邊系統(tǒng)會 Subscriber,然后是 Visualization Updater 會做自動的可視化方案生成,最后就是報警。
這里是一個存儲的架構, Kafka 的 MainFlow 在存儲這邊對接了三個模塊。首先是 存儲架構,我們把 metadata 和時序數據分開了。Metadata 是描述 Metrics 的 Tags 以及跟 Entity關系的數據, 我們把它全部拆出來,生成 UUID,接下來存儲的 時候拿這個 UUID 和 TimeSeries 存在一起。
Redis 的集群會緩存六個小時數據,會有一個模塊每半個小時把數據 merge dump一 次,在 mongodb 這邊分了幾個粒度的庫,有1分鐘,有5分鐘,有30分鐘還有一天。Archive 模塊每天跑一次,負責把 MongDB 中的數據歸檔到 hdfs。
接下來第三個模塊是 Visualization Updater,通過訂閱數據,按數據的組織形式來 生成與數據相對應的可視化的配置。大部分情況下用戶只要推數據,就可以在系統(tǒng) 中看到圖表,接下來他想要定制細化、業(yè)務化的一些視圖的時候,可以再拖拉這些圖表。
用戶或者第三方的下游平臺會通過我們統(tǒng)一API和UI拉數據,拉數據的過程有一個策略讀的模塊,這個模塊主要責任決定從哪里讀數據,按用戶的query索引到要取哪些 UUID的數據,然后接下來按用戶的讀取方式決定從哪個庫取數據。
比如說要展示圖表,然后取最近一小時的數據,就直接從 Redis 里面取一小時,如果取一天的數 據,我們就會做一次降級,因為一分鐘的點會非常密,也是不利于觀察,這時候我 們會直接把它降成5分鐘的粒度,從 MongoDB 讀。如果取更久的數據,我們就可能依次降級,做更友好的可視化。
3. 數據可視化和報警
說完存儲,我們簡單說一下可視化方面,比較通用的功能就不說了,重點說一下比較有意思的地方。之前提到我們對監(jiān)控對象抽象了一層概念,有 EntityTypes、Entities、Tags。這種情況下可以實現任意組織架構的業(yè)務視圖組裝。
只需要選擇你要看哪些 EntityTypes,這些 EntityTypes 之間的關系通過 Tags 描述,然后就可以構建出樹形的組織架構,這個樹形架構就可以直接關聯到所有相關 Entities 的數據和圖表。
這里有一個例子,最典型的機器視圖,有 project,groups、machines,組成一個三層樹形結構。
目前我們有 200 多個自定義視圖,比如這個機器視圖,這個用戶使用的容器視圖, K8S-Pod-Container的層級。
這里是監(jiān)控后端的視圖,把 Arbiter-Region-Node 這三個層級渲染上去了,然后最下面這一層看到的就是所有 Node 節(jié)點的信息。
同時如果你想做聚合,不需要上報多次數據,比如這里只需要 Node 報自己的數據,根 據這個視圖的組織關系配一個向上聚合的規(guī)則,然后前面那個聚合模塊就會幫我們搞定所有聚合相關的事情。這時候其實你只要點到 Region 節(jié)點就可以看到所屬 Nodes 總共加起來有多少數據。
我們之前發(fā)現一個很典型的問題,報警這個東⻄很難調試。我們配了一個規(guī)則,但不知道這個規(guī)則是否能生效,所以需要造數據測試。基于這個痛點,我們迭代新系統(tǒng)的時候,所有的功能是基于所⻅即所得的原則去做的。
比如說這里用戶給一些 tags,篩選出一批數據,這些數據就會直接呈現在圖表,同時給出一些統(tǒng)計數據,比如說均值、percentile。輸入閾值,就會在圖表顯示出來閾值和數據的相對關系,做報警模板調試的時候也是類似的,只要選一條已經存在的數據,就可以按這條數據直接按模版渲染報警出來。
報警主要做了如下的策略,首先是指標閾值,然后是變化率,還有一些用戶自定義的異常消息、異常檢測,還有組合報警。
用戶配完這些報警之后,可以用策略模板分享出來的。
我們會有幾百個項目,有很多運維人員在維護,有些項目可能是同構或者說是類似架構的,這種情況報警策略很多都是相同的,我們可以用策略模板做分享和訂閱,減少人工配置的成本。
再簡單說一下收斂方面做的事情,我們基于整體的規(guī)則引擎處理數據,產生問題, 后面有一些問題合并模塊做合并,目的是盡可能減少報警。
在合并策略上做了一些人工策略,比如說我們可以選擇做一個十秒鐘的合并,相當于做了一定報警時效性,同時增加報警的準確性。另一方面我們也會根據項目、分類、策略等維度做報警合并。
除此之外,與CMDB關聯可以做到更多合并策略,例如CMDB能夠描述網 段和機器的關系,就可以做到網絡層到機器層到的合并。
問題產生之后,我們做了一個策略確保問題會被及時處理。產生新問題之后,首先 通知值班,有各種方式,比如泡泡、郵件、電話、短信之類。
如果這個值班正好手機不在旁邊,確認超時,就會通知到下一個backup值班,后面還有2級值班,3級值班,如果都沒有通知到,再回來通知,這樣一個方式確保了報警的可達性。
這個過程只要有任意一個人收到消息點擊處理問題,報警就被抑制了。對于一些指標類的 報警,我們也做了一個指標恢復正常時自動關閉問題的邏輯。
4. 智能監(jiān)控實踐
說完報警相關的事情,再說一下我們再智能監(jiān)控方面的一些嘗試。
首先,傳統(tǒng)的報警中,像閾值、同環(huán)比用的都比較多,能解決很多問題,還是有一些情況是沒辦法覆蓋的。異常檢測能做到很多事情,一方面能夠按照數據的特征去找出異常點。另一方面能夠增量學習,適配數據變化。
比如說,以前某個數據維持在一個基線上面,如果我們用閾值,有一天這個數據明顯偏離了基線,然后又⻓期穩(wěn)定下來的,就需要調閾值。而很多異常檢測模型可以通過線上的增量學習更新模型適應變化。
我們整體流程大概是這樣的,首先是數據的抽取、存儲還有標注,接下來做一系列的預處理,比如對于非對稱的樣本做重采樣,一些標準化的脫敏等。我們在特征工程方面做了蠻多的努力,目前我們線上比較有價值的特征大概360多個。
接下來是模型訓練的流程,我們線上已經有一些無監(jiān)督和有監(jiān)督的模型在跑,也做 了一些集成,同時這里會有一個模型實時評估反饋。模型訓練完之后就會丟到S3的 存儲上,有一套線上實時檢測流,訂閱模型的變更,拉取模型,訂閱數據做檢測和報警。
我們這邊嘗試過一些模型。
首先說說最傳統(tǒng)的統(tǒng)計學基于距離、密度等的方法,這些方法有一個共同特點,使用特別簡單,不需要標注,但是有一個問題就是效果隨緣,它們在一些場景下表現的很好,另一些場景下,數據特點不同,表現的就很差。
第二個階段我們嘗試 IsolationForest ,這個算法是我們⻅過無監(jiān)督算法里面最好的 一個,它的 Baseline 相當高,在大部分場景下能得到比較好的效果,基本上不用太多調試。但也有另外一個問題,上限一般,畢竟無監(jiān)督,沒有標注介入,事實上很難按你的意圖區(qū)分很多細節(jié)情況,比有監(jiān)督來說還是有差距。
最后還是走有監(jiān)督的路子,一開始嘗試了LSTM、DNN等模型,后續(xù)后是回歸到比較基礎的樹模型,當然也有嘗試做集成,目前集成模型效果會更好一些。
接下來我們發(fā)現一個問題,就是在更新樣本集和特征的時候,會發(fā)現當我們想要滿足一個場景,就有可能會對其他的場景造成誤導。
比如說有一些業(yè)務的曲線本身抖動比較厲害,它需要對大幅度抖動進行報警,如果我們把這類樣本直接導入樣本集,就可能影響比較平滑的曲線檢測。當然有人說我們可以對前面曲線做平滑,再進模型,但這樣的話其實會降低抖動幅度比較厲害的曲線的峰值,也會影響到結果。
所以這種情況下我們就嘗試做一個曲線分類,抽取一些曲線特征,比如說自相關系數,比如 說抖動幅度相關的特征,用這些特征來做一下數據分類,根據不同的分類來預訓練 模型,嘗試解決這個問題,這樣在某一個場景下我們加入樣本就不會影響到另外一個場景的模型。
簡單說一下我們的模型,預處理后有features展開,然后用xgb做了特征選擇,然后 有SVM、RF、GBDT等一系列的弱模型,最后用LR做一個簡單的ensemble。這套模 型在我們的十萬條曲線上測試的結果大概是85%的precision,因為我們是重點對 precision做優(yōu)化的,recall會稍低一些。
說完異常檢測說說另一個話題,我們在嘗試去尋找問題之間的關系,引入了關聯分 析。這里的關聯分析主要是指時間序列的關聯分析,我們的目標是從幾百條曲線中 定位出故障原因或者確定故障影響范圍,或者確定影響整體指標的局部指標。
整體流程是:從報警觸發(fā),通過CMDB的業(yè)務配置和一些策略,確定要搜索哪些曲線,然后獲取數據,跟當前發(fā)生報警這條曲線做相關性計算,最后按相關性排名, 推送用戶,用戶這時候也會有一些反饋,我們拿回來之后做相應優(yōu)化。
這是一個簡單的模型介紹,一開始我們嘗試了一些曲線相似性計算的模型,一直沒有取得很好的效果。
后來我們看到一篇論文,它的觀點很有意思,不去搜索兩個曲線之間的關系,只搜索一個事件和曲線之間的關系。
因為我們知道前面的曲線已經有問題,我們按這個時間節(jié)點,對需要搜索的曲線前后劃分子序列,抽出兩個子序列出來,再到這條曲線上隨機取一個子序列,再對比這三個子序列之間是不是相同,如果前面的子序列和隨機子序列不同,我們認為這個序列的變更導致這個事件,如果是隨機子序列跟后面那個子序列不同,我們就認為是這個事件導致這個序 列的變更,這樣就可以大致構建出曲線之間影響的鏈條,形成一個傳播鏈。
這里面有一定的偶然性,隨機子序列的選擇非常影響模型的效果,我們也嘗試隨機多次選擇,合并結果的方式,去降低它的偶然性。
整體而言,這個模型在測試效果中比直接計算曲線相關性要好不少。
我今天分享差不多到這里結束,謝謝。