注:本文由网易游戏监控团队负责人王卫东先生在GOPS2019·上海站。作者简介网易游戏监控团队负责人王卫东,给大家介绍一下我们在游戏领域是如何做到千亿级监控体系的,以及我们在智能监控方面的一些探索。我是网易游戏监控组的负责人。从事运维平台相关开发工作7年。擅长智能监控和应用性能调优。我将把它分成四章。首先,游戏领域的监控有什么不同,全球游戏监控有哪些挑战?我们如何处理海量时间序列;第三部分讲了可视化和报警地方的亮点,最后讲了我们在智能监控方面的实践。1.监控全球游戏的挑战首先,让我们谈谈传统的游戏架构。过去,大多数游戏架构都是单服务器、单机的。另外基础设施比较简单,之前基本都是物理机。另外,过去我们基本上是针对国内市场做事的。最后,监控的层面也很简单,无非就是硬件、网络、操作系统、进程和业务指标。但现阶段,我们面临的监控场景变化太快,游戏结构多样化,混合基础设施逐渐在我们公司出现。此外,公司开始在海外站稳脚跟,在海外取得了非常好的增长。最后,传统的监控也在逐渐向可观察性扩展。首先是游戏架构的改变,从最初的单机架构到分布式架构。也就是说,玩家看到一个游戏服务器,后面会有十几台机器,有的甚至有上百台机器,这要看玩法。后来很多游戏的开发都接触到了微服务的概念,开始逐渐将大厅、聊天服务等游戏从游戏的核心逻辑中分离出来,变成微服务,为游戏服务提供支持。在这种情况下,微服务场景开始逐渐出现在游戏场景中。第二个方面,我们一直在做云上的游戏。一开始我们把游戏服务器部署在物理机上,后来我们做了私有云,部署在虚拟机上。在出海的过程中,也开始逐步采购海外公有云和第三方IDC机器。后来我们开始做容器化。容器化发展到一定程度后,现在有一些游戏在尝试云原生。但我们的过程不会在一夜之间发生。毕竟体量比较大,一个公司可能有上百款游戏。在这种场景下,会出现混合云状态。有的游戏还是物理部署,有的游戏已经部署在云端。原来,在这种情况下挑战是非常大的。另一方面,我们公司目前的游戏业务已经覆盖了全球几十个国家,监控中将有20、30个地区覆盖全球的游戏服务;此外,我们还会在海外收购多家云服务商。在这种情况下,监控挑战也会增加。在从传统监控过渡到可观察性的过程中,我们不仅有告警和可视化,还有调试和分析。尽量让系统更加透明和可视化,从而形成更好的理解来优化我们的产品,实现更好的衡量,形成一个良性的闭环。基于以上思路,我们现在的游戏监控架构是这样的。从下往上是监控数据从生成到处理再到消费的过程,然后右边会有一些控制层的东西。这张图只画出了一些最关键的点。我们会在采集层面做大量的数据录入,比如SDK、agent、日志指标、第三方数据库等。通过部署在多个区域的就近接入层,将这些数据接管并导入到中心。该中心将使用Kafka数据队列进行解耦和路由,支持多系统数据订阅,以及聚合和数据存储。在数据应用的迭代过程中,我们会有一些历史包袱。一开始我们监控业务场景,所以会有一堆数据子系统,比如客户端监控、用户体验监控、服务端监控、资源相关监控、网络监控、性能优化监控等。我们目前正在一个一个进行整合,对外提供一个统一的入口。警报级别基于用于警报的标准化规则引擎。现阶段,我们正在逐步加入异常检测、事件关联等功能。此外,还有问题生命周期管理和事件升级等机制来确保通知的可达性。在顶层,我们提供了数据可视化、告警通知、实时分析、性能优化等一系列能力。在管控层面,我们通过与CMDB深度集成,订阅CMDB变更,降低监控的配置成本。全球监测是通过区域管理实现的。我们的agent可以支持丰富的插件定制功能,所以就有了插件库。最后我们做了一个commandpipeline,其实就是Ansible之类的东西,集成了我们的agent,最直接的价值就是可以基于这样的基础设施来构建配置分发,故障自愈等功能。2.海量时序数据处理接下来说说我们在面对海量时序时要做的一些事情。首先,面对海量、异构的监控场景,我们对监控对象进行了抽象,所有的监控概念都可以定制到抽象的数据模型中。通过与CMDB的结合,我们实现了相对较小的管理成本。在采集方面,我们提供多种采集方式来适应不同的业务场景,然后做一个统一的入口,在统一的数据总线上做数据对齐和预处理。最后,我们做了一个大规模的海量时间序列存储。首先说一下监控对象的抽象。我们为什么要做这个?在正常的场景下,我们会监控一台物理机,一台虚拟机,一个容器,然后是一个进程。对于CPU、网卡等硬件,只要能标注出来,就可以关联数据。随着业务的不断扩展,在游戏场景中,需要监控某个游戏场景,某个战斗,某个NPC的属性,以及游戏进程间RPC的情况。在这种场景下,如果我们硬编码配置,适配各个场景,对于监控人员来说,维护成本是非常高的。所以我们做了一层抽象,这和业界流行的解决方案如OpenTSDB、Prometheus有一些共同点。我们把监控对象抽象成Entity,用EntityType来描述它属于什么类型,用tags来描述它的属性。同时,标签也会有一个类似于交叉表的作用,将实体链接在一起。目前,我们有100多个EntityType,近500万个实体和4亿个时间序列。刚才提到了全局最近的接入层,也就是服务端监控的基础设施。中间红色的是Arbiter,作用是仲裁。负责订阅CMDB的变化,生成监控配置,监控配置会分发到各个区域。当代理连接到网络时,它会首先询问Arbiter。我是哪个地区的?然后Arbiter会告诉它它所属的区域和节点列表。代理将尝试连接,如果成功,它将连接到网络。它将与节点保持长连接,并将其产生的所有数据交给节点中转。我们会在节点到中心的时候做网络优化,这比直接把代理连接到中心要快很多。另一方面,保持长期连接有助于配置同步。当配置发生变化时,Arbiter会通过Node和长链接将配置实时推送给Agent。这套架构有高可用的保证。首先,Arbiter是单点的。我们已经实现了ArbitersActive-Standby模式。当出现问题时,StandbyArbiter将接管主Arbiter的工作。Arbiter的逻辑基本是幂等的,所以不用担心数据和集群的一致性。Node将与Arbiter保持心跳。如果Node失去连接,Arbiter会派遣相关的Agent到其他节点。此外,每个区域都会有多个节点冗余。在节点之间分配代理时,我们使用一致性哈希来确保在添加或删除节点时,代理分配关系的抖动最小。在这个过程中,配置管理部分和数据流部分是分离的。当上层Arbiter与节点完全断开连接时,代理会一直与节点保持连接,直到收到新的配置。这样的话,即使我们中心出现故障,agent还是会上报数据。说说我们的地区是怎么划分的。首先,我们将有一批机器和它们的IP。CMDB会通过IDC、ISP等一系列信息对IP进行分类,划分CMDB区域。当一个新的CMDBregion产生时,我们会得到changeevent,根据地区、ISP等几个条件判断它可能离哪个monitorregion比较近,或者相关的网络质量比较好,然后在几个candidates中选择一些regions指向,发起与新region的互检测任务,得到rtt和loss。管理员可以看到一个包含这些数据的决策列表,只需选择一个监控区域,新的cmdb区域就会自动添加到监控区域中。通过订阅CMDBS的变化实现最低的管理成本。我们的SRE通过各种管理系统和CMDB来管理一些资源和业务之间的对应关系。Arbiter为需要生成配置的数据做一个内存中的ORM,同时在CMDB上连接一个dbtrigger,将其所有的增删改查发送给MQ,在arbiter中进行订阅,在arbiter中更新ORM即时的。然后,基于这个ORM,生成配置。配置生成后会推送到region节点,再推送给agent。初始架构完成后,我们可以实现秒级的配置更新,但是后续的规模会变得非常大。维护秒级配置更新使用的资源不划算,所以后面加了一个窗口,十秒或一分钟内变化的事件统一分发,尽量减少配置抖动。在数据采集方面,由于游戏面临的场景较多,我们提供了多种采集方式。首先是代理插件,通过服务绑定到机器上,收集机器上的数据,然后推送出去。主动监控的检查器插件也是类似的解决方案。我们为每个区域提供检测点,以便从外部发起主动检测。此外,还有一组框架允许用户自行配置并从任何主机发起检测。主动监控支持icmp/tcp/http等一系列协议。另外,SRE可以直接开发插件,覆盖业务协议。第三种方式是pusher。我们提供了一套SDK和服务器端。主要场景是将进程内部的数据推送到外部。游戏开发只需要在自己的代码中引入SDK,调用几个接口,然后推送数据即可。目标是能够在任何环境下实现数据推送接口。最后,游戏经常使用日志来暴露一些统计指标。在这种情况下,我们还做了日志指标兼容,从实时日志流中过滤一些标准字段,并将其定向到监控数据流中。另外,公司也在尝试云原生的方案,所以我们也引入了prometheus,我们集成了它的服务发现模块,然后直接接入prometheusexporter协议,使用一套分布式代理拉取exporter暴露的数据,这样就可以直接对接k8s、etcd等系统的监控了。对于容器监控,我们将cadvisor封装成插件。为了兼容公司其他时序数据,我们提供了第三方的dbadapter,可以从其他DB导入数据。通过这些数据采集方式,我们可以更轻松的应对混合云下的监控场景。针对物理机和虚拟机,运行agent,SRE按需编写并绑定插件;对于容器,我们更喜欢使用pusher或者log来导出数据;对于相对固定的场景,也支持直接从宿主机附加到容器中采集数据;cloudNative场景使用Cadvisor、PrometheusExporter、日志指标等,刚才说了我们的agent是一个插件化的框架。目前我们使用Python来做这套代理,所以对于SRE来说入门成本非常低。如果SRE想开发插件,可以点击系统获取一个Gitlabrepo,在代码框架中填写采集数据的代码,推送上去。只要你push到保护分支,就会自动打包成pip包,然后丢给pip源。接下来只需要将插件绑定到服务上,这些服务的机器就会得到这个配置,然后代理就会安装插件运行。现阶段我们有超过800个Python插件,覆盖了绝大部分的业务场景。包括上面提到的多点检测和故障自愈功能,都是插件支持的。当数据采集完成后,我们有一个统一的总线进行处理。首先,所有的数据都会进入一个KafkaOriginDataTopics,然后PreProcessor会做一些数据清洗,过滤非法数据。做数据对齐,然后进入MainFlow。进入MainFlow,我们会有一个FlinkAggregator来负责聚合。用户会在系统上配置一些可视化和告警规则,并根据这些规则生成一些统一规范的聚合规则。Flink规则聚合数据,然后将聚合后的数据抛回给MainFlow。未来会有几个系统订阅,首先是存储,然后是周边系统作为Subscriber,然后是VisualizationUpdater,自动生成可视化方案,最后是报警。这是一个存储架构。Kafka的MainFlow在存储端连接了三个模块。首先是存储架构。我们将元数据和时间序列数据分开。元数据是描述Metrics的Tags以及与Entity的关系的数据。我们把它们都拆开生成UUID,存储的时候把这个UUID和TimeSeries一起存储。Redis集群会缓存六个小时的数据,每半小时会有一个模块进行数据的合并和转储。在mongodb方面,有几个粒度库。有1分钟、5分钟、30分钟和一天。.Archive模块每天运行一次,负责将MongDB中的数据归档到hdfs中。接下来的第三个模块是VisualizationUpdater,通过订阅数据,根据数据的组织形式生成数据对应的可视化配置。大多数情况下,用户只要推送数据就可以看到系统中的图表,然后当他想自定义一些详细的、面向业务的视图时,可以拖拽这些图表。用户或第三方下游平台将通过我们统一的API和UI拉取数据。拉取数据的过程有策略读取模块。该模块主要负责根据用户的查询索引决定从哪里读取数据,取哪些UUID数据,再根据用户的读取方式决定从哪个库取数据。比如你要展示一个图表,然后取最近一小时的数据,直接从Redis中取一小时即可。如果你取一天的数据,我们会做降级,因为一分钟的点会很密集,不利于观察。这个时候我们就直接缩小到5分钟的粒度,从MongoDB中读取。如果我们需要更长的数据,我们可能会依次降级以进行更友好的可视化。3.数据可视化和告警说完存储,再简单说说可视化,比较笼统的功能就不说了,重点说说比较有意思的。前面说过,我们抽象了一层监控对象的概念,包括EntityTypes、Entities、Tags。这样就可以实现任意组织结构的业务视图组装。只需选择要查看的EntityType,这些EntityType之间的关系由Tags描述,然后就可以构建一个树状的组织结构,可以直接关联到所有相关Entity的数据和图表。这里举个例子,最典型的机器视图,有project、groups、machines,形成一个三层树形结构。目前我们有机器视图、用户使用的容器视图、K8S-Pod-Container层级等200多个自定义视图。这里是监控后台的view,渲染了Arbiter-Region-Node三个层级,然后底层看到的是所有Node节点的信息。同时,如果要做聚合,不需要多次上报数据。比如你只需要Node上报自己的数据。根据这个视图的组织关系,配置一个向上的聚合规则,然后前面的聚合模块会帮我们处理所有聚合相关的问题。事物。这个时候其实只需要点击Region节点就可以看到Nodes一共属于多少数据。我们之前发现一个很典型的问题,告警很难调试。我们配置了一条规则,但是不知道这条规则是否会生效,所以我们需要创建数据进行测试。基于这个痛点,我们在迭代新系统的时候,所有的功能都是本着所见即所得的原则。比如这里用户给出一些tag,筛选出一批数据,数据会直接显示在图表上,同时给出一些统计数据,比如均值和百分位数。输入阈值,阈值与数据的相对关系将显示在图表中。调试告警模板时类似。只需要选择一个已经存在的数据,就可以直接根据这个数据按照模板渲染告警。告警主要采用以下几种策略,首先是指标阈值,然后是变化率,还有一些用户自定义的异常信息,异常检测,组合告警。用户配置好这些告警后,可以使用策略模板进行共享。我们会有几百个项目,很多运维人员在维护。一些项目在结构上可能是同构的或相似的。这种情况下,很多告警策略都是一样的。我们可以使用策略模板进行共享和订阅。减少手动配置的成本。先简单说一下收敛。我们基于整体规则引擎处理数据并产生问题。后面会合并一些问题合并模块,尽可能的减少告警。在合并策略上实施了一些手动策略。比如我们可以选择做10秒合并,相当于做了一定的报警时效性,增加了报警的准确率。另一方面,我们也会按照项目、分类、策略等维度合并告警。另外,通过与CMDB的关联,可以实现更多的合并策略。例如,CMDB可以描述网段和机器之间的关系,可以实现从网络层到机器层的融合。问题出现后,我们制定了策略,确保问题得到及时处理。出现新问题后,先通知值班人员,有泡泡、邮件、电话、短信等多种方式。如果值班手机不在附近,确认超时后通知下一次备份值班,之后有二级值班和三级值班。没有通知就回来通知。性别。在此过程中,只要有人收到消息并点击处理问题,警报就会被抑制。对于一些指标类的告警,我们也实现了指标恢复正常后自动关闭问题的逻辑。4.智能监控实践说完告警相关的事情,我们再来说说我们在智能监控方面的一些尝试。首先,在传统告警中,阈值和同比比率用的比较多,可以解决很多问题,但还是有一些情况无法覆盖。异常检测可以做很多事情。一方面,它可以根据数据的特征发现异常值。另一方面,它可以增量学习并适应数据变化。比如过去某个数据一直保持在基线以上,如果我们用一个阈值,有一天数据明显偏离基线,然后很长时间稳定下来,就需要调整阈值。并且许多异常检测模型可以通过在线增量学习更新模型来适应变化。我们整体的流程大致是这样的,首先是数据的抽取、存储和标注,然后是一系列的预处理,比如针对非对称样本的重采样,一些标准化的脱敏等。我们在特征工程方面下了很多功夫。目前,我们有超过360个更有价值的在线功能。接下来是模型训练的过程。我们已经有一些无监督和有监督的模型在线运行,我们也做了一些整合。同时,会有模型的实时评估反馈。模型训练好后,会落入S3存储。有一套在线实时检测流,订阅模型变化,拉取模型,订阅数据检测报警。我们在这里尝试了一些模型。首先说说最传统的基于距离、密度等的统计方法,这些方法有一个共同的特点,使用起来非常简单,不需要标注,但是有一个问题就是效果是随机的,并且它们在某些情况下表现良好。在某些场景下,数据特征不同,性能很差。第二阶段,我们尝试了IsolationForest。这个算法是我们见过的最好的无监督算法。它的基线相当高,不需要太多调试就可以在大多数场景下得到更好的结果。但还有另一个问题。上限是平均值。毕竟没有监督,没有标签干预。其实很多细节是很难按照你的意图来区分的。与监督相比,仍有差距。最终还是走的是有监督的路子。一开始尝试了LSTM、DNN等模型,后来又回归到比较基础的树模型。当然,我也尝试过整合。目前,集成模型的效果更好。接下来,我们发现了一个问题,就是我们在更新样本集和特征的时候,会发现当我们想要满足一个场景的时候,可能会误导其他场景。比如有些业务的曲线抖动比较大,抖动大需要报警。如果我们直接将此类样本导入样本集中,可能会影响更平滑的曲线检测。当然,也有人说我们可以把前面的曲线平滑后再进入模型,但是这样实际上会降低抖动严重的曲线的峰值,也会影响结果。所以在这种情况下,我们尝试做一个曲线分类,提取一些曲线特征,比如自相关系数,比如抖动幅度相关的特征,利用这些特征做数据分类,根据不同的分类预训练模型。尝试解决这个问题,使得在一个场景中添加样本不会影响另一个场景中的模型。让我们简要谈谈我们的模型。预处理后扩展特征,然后使用xgb进行特征选择。然后是SVM、RF、GBDT等一系列弱模型。最后用LR做一个简单的ensemble。这个模型在我们100000条曲线上的测试结果是85%左右的precision,因为我们着重优化precision,recall会略低。说完异常检测,我们再来说说另一个话题。我们试图找到问题之间的关系并引入关联分析。这里的相关性分析主要是指时间序列的相关性分析。我们的目标是从数百条曲线中定位故障原因或确定故障范围,或确定影响整体指标的局部指标。总体流程是:从告警触发,通过CMDB业务配置和一些策略,确定搜索哪些曲线,然后获取数据,与当前告警曲线做关联计算,最后根据关联排名推送用户。时不时会有一些反馈,我们拿到回来后会做相应的优化。这是一个简单的模型介绍。一开始,我们尝试了一些计算曲线相似度的模型,但都没有取得很好的效果。后来我们看到一篇论文,观点很有意思。我们不搜索两条曲线之间的关系,而是只搜索事件与曲线之间的关系。因为我们知道前面的曲线有问题,所以我们根据这个时间节点划分要搜索的曲线前后的子序列,提取两个子序列,然后在这条曲线上随机选择一个子序列,比较三个子序列是否是同样,如果前一个子序列与随机子序列不同,我们认为是这个序列的变化引起了这个事件,如果随机子序列与后一个子序列不同,我们认为这个事件引起了这个序列的变化,所以可以粗略地构建曲线之间的影响链,形成传递链。这其中有一定的偶然性。随机子序列的选择极大地影响了模型的效果。我们也尝试随机选择多次并合并结果以降低其偶然性。总的来说,这个模型在测试效果上比直接计算曲线相关性要好很多。今天分享到这里就差不多了,谢谢大家。
