在开始进入主题之前,我想先给大家科普一个名词——可观测性,它主要有Tracing(链路追踪)、Logging(事件日志)、 Metric(聚合指标)三个大部分,我今天分享的主题主要围绕的是Metric部分。
为什么叫聚合指标?因为我们收集存储的指标很多,大部分场景下我们关注的是指标的聚合值,比如1分钟内或者5分钟内的最大值、平均值等,或者是像一些 Top10的数据,或者是一些分位值,比如95分位或90分位,代表的产品大家也或多或少地接触过,比如ELK是Logging的一个代表,Prometheus是近几年比较火的。
一、自研时序数据库EyesTSDB的介绍
第一部分我会从概览、架构设计、核心功能、应用场景、问题与挑战5个方面向大家介绍我们自研的时序数据库EyesTSDB。
1、概览
我们自研的时序数据库是支持全球监控的,它覆盖了全球10多个国家和20多个地区,也支持国内私有云、公有云和混合云的监控,能够同时满足公司游戏出海的监控需求。
我们有多种监控方式,包括基础监控、网络监控、进线程和业务自定义等监控。
我们也有丰富的数据采集方式,目前有1000+个Agent采集的插件,SDK上报或通过HTTP等网络协议上报监控数据,还支持内部的日志数据转化,以便程序开发时能够快速接入我们的监控平台。
另外我们的服务对象包括我们的基础设施,有超过30万的物理机,以及云监控、k8s的容器监控,服务产品包括公司的头部产品,比如梦幻西游、大话西游、阴阳师等。
上图也列举了我们目前的量级,我们现在的监控实体有1500万,总的持续指标数量达到了15亿。
2、架构设计
接下来介绍我们的架构设计,这是一个简化版的架构图,可以分为上下两个部分,上面是监控中心,下面是Region区域。
1)监控中心
监控中心包括前端系统UI和报警界面,以及下面的监控调度、数据总线、数据存储和插件仓库,这几方面后面都会详细介绍。
2)监控Region
监控Region主要包括Region节点以及被监控的机器,被监控的机器会安装一个Agent插件。
- Arbiter:我们的监控调度中心,也是一个核心的模块,它会结合CMDB生成监控机器的配置,也负责Region的保活和调度,使用Active-Standby的主备模式保证高可用。
- 数据总线:主要负责收集监控数据,然后通过kafka流转。
- 数据存储:我们设计了冷热存储,以存储我们的时序数据。
- 插件仓库:是我们的一个特色,会联动gitlab保存用户的插件,用户可以通过自定义插件代码实现想要的任何监控方式。
- 监控调度:结合cmdb生成配置,负责Region的保活与调度,使用Active-Standby的模式保证高可用。
- Agent:负责采集监控指标上报到Region,统一由Region上报到数据总线,包括核心模块、系统插件、网络插件以及用户自定义插件。
右边的图是一个服务端监控的基础架构。当CMDB发生变更时,Arbiter会去订阅变更,然后生成一个最新的配置,下发到Region区域,比如Publisher收到变更之后,会同步到agent去询问,agent会询问Arbiter:我应该到哪一个Publisher上报数据?然后Arbiter会告诉它所属的Region和Publisher列表,agent尝试连接,成功就会入网,它会与Publisher保持一个⻓链接,把它产生的数据全部交给Publisher去中转。Publisher到中央我们会做网络优化,比agent直接连到中央会快很多。
3、核心功能
1)监控对象设计
接下来给大家讲一下我们的监控对象设计,这也是我们的核心功能之一。
我们把监控对象抽象为Entity,用EntityType描述它是属于什么类型的,用Tag描述它的属性。同时tag也会有一个类似交叉表的用途,把entity关联在一起,比如机器是一类实体,进程是一类实体,而具体的机器名如hostname01是机器的一个Entity,正在运行的agent进程是进程的一个实体,最终我们可以通过Entity或Metric的tag过滤到我们期望得到的TimeSeries(时序数据)。目前我们大概有500+个EntityType,1500万Entity实体模型,覆盖了大约15亿的时序指标。
2)自定义插件监控(python)
我刚才提到了有一个测试是我们的自定义插件,这里讲一下它的工作流程。
①首先用户在前端界面创建插件,我们会自动创建一个gitlab仓库;
②然后用户会在这个仓库里编写插件代码,代码提交后,会触发pip包的构建;
③监控调度中心会生成Agent所需的插件配置,然后下发到Agent;
④Agent获取到插件信息后,从pip源拉取pip包并安装运行。
这里也给大家看一下我们插件的代码量,例子它其实我们这里定义了一个基类,用户只需要实现这个sample方法即可。
3)数据总线
插件安装之后,监控数据是怎么流转的?这是由我们的数据总线控制的。
各种上报端的数据汇总到kafka后,我们会有一个模块进行数据的清洗与预处理,然后再流转到下一个kafka,用于存储、报警或者其他订阅系统。同时我们也有一个预聚合的模块用于对用户关注的数据进行一次预聚合,能够减少数据量,加快数据查询,预聚合的规则可以从我们的UI上进行配置。
4)数据存储
下面介绍我们的数据存储,它具有冷热分离、过期删除和HDFS永久存储的特点。
- 冷热分离:数据总线会流到kafka,由数据的写入模块负责写到我们的热数据库,我们使用Redis进行热数据的存储,只存每个实体6小时的数据,然后通过冷数据写入模块每半小时把Redis中的数据写到MongoDB的冷库中。
- 过期删除:我们的冷库分成4种粒度:1分钟、5分钟、30分钟、1天,当用户查的时间范围不同时,我们会根据时间范围选择不同粒度的数据库查询。每一种粒度的保存周期不一样,比如1分钟只保存7天,5分钟保存30天,数据过期会自动删除。
- HDFS归档存储:每天通过归档写入模块把冷数据库中的时序数据存储到廉价的HDFS中,用于AI、异常检测等数据分析的业务场景。
当用户发起查询的时候,我们会根据时序数据库的UUID拉取到时序数据,再根据UUID查询到对应的时序数据,然后把冷热数据合并完再返回给用户。
4、应用场景
接下来给大家看一些应用场景,主要有4个应用场景,分别是机器视图、进程视图、容器视图和一个我们自定义的仪表盘。
1)机器视图
主要负责一些基础监控的机器,我们关注的基础指标有CPU、内存、IO等,我们会与CMDB做一些联动的,分群组、服务、机器三元组,一台机器可以绑定不同的服务,再绑定到一个群组上,我们就可以在这里面做不同的树形展示。
当点到具体某一个机器时,我们会展开这个机器的面板,可以看到详细的时序数据指标,也可以选择聚合方式,auto是一个时间粒度,刚才讲到我们会根据不同的查询范围选择不同的粒度展示给用户,面板左边也可以切不同类型的数据进行查看。
2)进程视图
这里展示的是我们的进程视图,我们可以把实体类型的tag做一个目录层级展示,比如我有三个进程,都属于一个进程组,我在这上面可以看到Redis最大的CPU使用率、最大的RSS内存使用率等信息。点到具体的每一个层级下可以展示每一个进程具体的指标详情,再点开就像刚才展示的机器视图的面板,会有更加详细的指标。
3)仪表盘
再给大家介绍一下我们自定义的仪表盘,用户可以在这里面配置实体类型。
仪表盘能够填写查询的指标,过滤条件Tag,这里支持灵活的括号逻辑表达式 比如(a|b)&c,同时能够按照tag进行分组聚合,也能指定返回的时间间隔,默认会根据查询范围算出一个合适的间隔。
配完之后即可按不同的类型展示我们的数据,比如这是一个大盘,它会展示我们整个项目运行了哪些服务,服务的基础指标使用的情况,也可以点开机器详情或一些其他的业务监控之类的。
5、问题与挑战
这一套监控系统运行了好几年,看起来也没有什么问题,但是我们知道科技发展是日新月异的,比如从之前的单体架构到分布式架构,以及到现在的service mesh的服务网格化,我们传统的监控也很难满足用户的需求。
- 整套的监控系统设计是基于分钟粒度设计的,最低只能存储1分钟的监控数据,无法满足一些敏感业务的监控场景,比如数据库的OP监控,1分钟会发生很多次,导致错过了波峰波谷,从而会导致DBA处理不及时而影响业务。
- 越来越多的云原生场景监控粒度都是秒级,业务期望也能支持到秒级的监控。
- 这一套TSDB基本是基于python写的,在海量的数据处理上,python显得有点力不从心,虽然我们使用golang重构了大部分模块,但是还有部分核心模块由于复杂性没有重构。
二、结合开源指标设计秒级监控
我们要怎么去支持秒级?其实我们也做过一些调研:
- Thanos,目前内部有用过OpenTSDB做存储,但是不支持正则查询,而且第三方存储、集群管理等需要较大的人力与资源成本。
- 原生的Prometheus是单机存储,无法支持集群横行扩容,也满足不了需求。
- EyesTSDB改造支持秒级,需要在Agent端支持秒级采集,然后存储端分粒度存储,基本属于推倒重建,收益不明显。
最终我们选择了一个支持集群部署的Prometheus远端存储,既能满足秒级、又能支持云原生等场景——VictoriaMetrics。VictoriaMetrics是一个开源的时序数据库,有以下几个特点:
- 开源时序数据库
- 快速高效可扩展
- 高性能监控系统
- 开源版永久免费
此外,它还支持上面讲到的Prometheus查询API,也有较高的压缩比,支持多种协议写入,也支持OpenTSDB的HTTP写协议。最重要的一点是它支持多租户,因为目前我们这套EyesTSDB也是多租户的,不同产品是按租户接进来的。
1、目录结构
首先介绍一下VictoriaMetrics的目录结构,它把数据和索引分开了,data是它的数据目录,indexdb是它的索引目录。
data数据目录里分成big目录和small目录,为什么要分成big目录和small目录?
这个主要是从磁盘空间占用上来考虑的。时序数据经常读取最近写入的数据,较少读历史数据。而且,时序数据的数据量比较大,通常会使用压缩算法进行压缩,所以历史的数据就放在big目录里,它会用比较高的压缩算法进行压缩,而small会用比较低的压缩级别压缩数据,small会定期合并成big。
index是使用保存周期保存的,主要有两个,一个是当前的保存周期,比如我配置了一个月的保存周期,那么一个月的数据都会存在这里,然后这是下一个保存周期的数据。索引支持保存最小时间和最大时间,便于做一些二分查找,能够快速定位到具体的数据位置。
2、数据结构
这里讲一下它大概的数据结构:
- AccountID和ProjectID是它的租户信息;
- MetricGroupID是它的指标分组ID;
- JobID和InstanceID是tag指标,比如JobID是第一个tag的哈希值,InstanceID是第二个tag的哈希值;
- 最后的MetricID是它的指标ID,是一个自增的时间戳ID。
3、整体架构
右边的图是它的整体架构,我们可以从中间看起,中间主要是存储,上下分别是查询和写入,它的查询是vmselect,协助是vminsert,两者是没有状态的,我可以随时扩增节点。
再下面是写入协议,它支持Prometheus的写入或InfluxDB的线路协议写入。
其中的存储其实是有状态的,但也支持我们的横向扩容,比如存储块码,我们可以加入节点支持数据厂写入。
目前它无缝支持Grafana和Prometheus API的查询。
以上是VictoriaMetrics的架构,我们怎么将其改造成为我们能用的架构?
4、新架构设计
这是我们目前一整套新的架构,中间红框部分就是我们刚才讲到的VictoriaMetrics的基础架构。
我们可以从上面入口看起,agent,比如我有一个exporter,它会写入到transfer,这个是支持Remote Write协议的,然后会流转到一个kafka,下面有一个proxy模块,负责数据的预处理和清洗,然后发给我们的报警系统做一些报警匹配和规则匹配。再下面是它的存储,左边是数据存储,右边是index的存储,这一方面我们有进行改造。
上面主要是一些API和UI的模块,底部我们可以看到右边有一个冷数据的存储节点,这一块会写入到冷数据,data集群里我们增加了一个Shuffle模块,它负责解析vm的文件,将同一个指标的所有时序数据发送到冷数据存储,然后由downsample降采样模块进行降粒度存到冷存储集群中。Data cluster有多套,而index cluster是公用一套,从而降低存储成本。
5、如何与内部CMDB联动
那么秒级和云原生我们都支持了,最后如何继承我们的CMDB联动?
刚才讲到我们的Entity和Metric都会有tag,而这些tag就包括我们内部的CMDB属性,比如右侧图。我们是将这些tag单独存了一份,与Metric的tag是分开的。它会把指标与TSID去做索引,最后是tag与MetricID去做索引,每一个tag它都会去做一遍索引。
TSDB分为两个模块,第一个是Meta信息,我们可以看到最上面一条例子,它是一个指标,后面是它的时间戳和值,Meta信息会包括指标名、labels。下面一部分是它的时序数据,timestamp和value就存在这边,中间通过一个ID关联起来。
大部分TSDB的不支持单个label key、多个label value,如 gid=1,gid=2。如何把CMDB的tag加到labels中?
我们是将其单独一份存储下来的,tag是用不同的index索引去存的,因为VictoriaMetrics本身有index的命名空间,不同的命名空间是不一样的,然后我们增加了命名空间去传数据,同时也增加了这些tag的增删查改接口以满足我们CMDB tag的写入和查询,再提供到不同的数据库去查询。
6、新增功能
1)vmshuffle
vmshuffle是我们自己做的一个功能,因为一个指标可能存储在不同节点上,而我们是把一个指标的数据统一汇总到一个存储节点上,然后定期存储到一个冷数据集群上。
目前支持的场景:
- 长时间以前一小段时间内的高精度数据查看
- 历史数据统计和分析
- 利用历史数据进行模型训练
我们如何把一个指标存储到一个节点上?其实我们是按它提供的一些原数据进行统一规整,比如MetricGroupID、ProjectID、AccountID、MetricName,哈希到不同的vmstorage里,同一个Metric下的数据会存储在相同的vmstorage里,然后经过Metric之后,数据会出现在文件的相邻位置。
2)vmdownsample
另外我们还做了一个降采样的功能,我们之前那套EyesTSDB有不同力度的降采样,那么这一套我们是怎么去支持的?
我们有一个备份集群,刚才讲的vmshuffle统一把数据存在冷数据集群,然后每一个节点会跑一个数据聚合的模块,然后去导,因为一个指标只会存储在一个节点上,把该指标一整块数据拉出来,比如一天跑一次,就把前一天的数据拉出来做聚合,然后按不同的力度,比如5分钟、30分钟、一天存储到不同的集群上,就能满足从之前的EyesTSDB到这一套的过渡,都能完全兼容支持。
7、问题与挑战
1)如何在查询侧让tag和label一样都能过滤到时序数据?
刚才讲到我们开辟了一个label的命名空间去存储的,其实它与原本存储的label命名空间是类似的,那么它就能够复用已有的支持的功能,比如label的promql的查询语句、过滤、聚合等功能。
2)如何满足已有的业务查询场景?
它已经支持promql的查询语法了,那么我们可以通过灵活的查询语句匹配到我们的仪表盘。比如我们之前提供的那些聚合方式,avg、min、sum等,它可以通过avg(avg_over_time)支持不同曲线、不同时间区间的聚合值,比如avg_over_time用作5分钟内的平均值,然后avg则是把这5分钟内的平均曲线再做一个平均,就能满足到上面提到的仪表盘的查询。
三、未来展望
1、数据质量
目前我们存储的数据比较多,但是有一些数据看起来其实数据质量不高,比如用户上报各种各样的数据,但他真正关注的可能只有20%~30%,我们后续会提高数据质量,提高我们存储的效能。
2、业务多元化需求
我们内部有多套集群,不同的集群有不同的需求,比如一套集群需要做到一些去重,一些集群不需要做CMDB关联,以及我们后续甚至会做一些SaaS化之类的。
3、Agent与调度中心性能
这是EyesTSDB那一套的性能优化,老的那套是Python写,中间也用GoLand进行重构,目前还在重构中,未完全重构。
4、回馈开源
最后主要是我们会开源,我们开发的一些额外的功能后续稳定之后,也会考虑反哺开源社区与大家共同发展一起进步。我们还有很多设想,比如自动化集群搭建、SaaS化集群管理,后面有机会再跟大家分享。
Q&A
Q1:这个数据库采用的是哪种冷热数据分离方案?是否会有数据丢失风险?
A1:新的这一套我们有一个热数据库,冷数据是从备份集群里同步下来的。数据丢失的风险方面,目前我们主库有一个储备,我们的各种降采样粒度的冷库也是都有储备的。
Q2:CMDB数据的准确性如何提升?有什么方案吗?
A2:这一方面我们EyesTSDB那一套其实有一些延迟,比如这么大体量的CMDB数据如何流到数据里面再存储下来,或者到报警部分其实是有延迟的。然后我们是有与CMDB系统合作做到一些实时同步,我们新的一套CMDB有一些拓扑结构,它用图数据库存储CMDB的数据,然后当中间某个节点发生变更时,它会触发一个变更消息,收到消息之后会把这个节点相关的信息都进行更新,这里涉及一些批量合并以及时间的策略,这套也是我们目前正在做的,基本上已经落地了,后面有机会再跟大家分享一下我们是怎么做的。