姚捷,唯品会平台架构部高级架构师,加入唯品会前有超过 10 年的金融/保险互联网技术架构和团队管理经验,擅长以产品思维设计和构建系统。现专注于互联网基础架构,负责唯品会全链路监控/分析平台的开发,管理,推广和运维落地工作。对大数据体系,实时计算,微服务体系,消息系统有深入研究和实践。
我来自唯品会平台架构部,非常感谢高可用架构在上海组织了这么一场高逼格的活动。唯品会平台架构部是长期致力于基础应用架构建设与推广的部门,我们承担非常多的基础应用架构研发。
唯品会有三大特点,特卖 + 闪购 + 正品,在唯品会,峰值访问量非常大,这样的流量,使得唯品会平台架构部承担非常大的挑战,包括我今天分享的全链路监控系统。
上面是我们的新广告,周杰伦代言,傲娇品牌,只卖呆萌的价格。
今天给大家带来的是我们一个比较重要的产品 Mercury,它是一个大型互联网应用全链路监控系统解决方案。
大家可能了解,500 错误场景可能是大家在线上经常遇到的问题,等待很长时间,然后系统给你 500,让大家很抓狂。在部署全链路监控之前,大家可能需要使用比较古老的方式,跑到生产机上面去查问题,这个过程可能耗费大量的时间。
之前在唯品会,我们做了一个叫 Logview 的产品,它是一款基于 nginx access log 的监控平台。唯品会大量系统前置了一个代理服务器,Logview 的机制是通过解析 nginx access log,构建一个日志监控平台。它跑了蛮长时间,迄今还在线上稳定运行,它曾经是唯品会最重要的一个监控平台。
但随着业务规模的不断增加,基于 nginx access log 的监控会遇到不少问题。
-
没有办法从代码级别进行监控,通过它只能看到粗粒度的监控信息;
-
随着服务化的改造,大部分的流量,已经不经过 nginx 了;
-
它支持告警,但是没有办法通过告警跟踪到问题的 root cause。比如说 500 通常是某一个异常导致的,但是这里是看不到异常的,你还得到主机上、到 ELK 上查日志。它没有办法通过告警很快速的定位到我们线上的 root cause,寻找问题的成本还是很高。
-
告警规则配置呆板,维护变更代价大。
-
没有办法跟踪业务之间的调用关系。唯品会在全网大概有上个业务域,主机现在接近几万台,这个体量是非常之大的,当一个系统一个域出了一个业务问题的时候,其实导致它产生问题的地方可能是很深层的。Logview 没有办法帮你跟踪到这些业务之间的关联关系,调用关系没有建立起来,出了问题你不知道最根本的原因在哪个域。
-
没有办法快速定位应用性能瓶颈。上面也提到,应用出了问题没有办法关联异常。
-
高峰值期间流量一上来就经常挂。
因为上面种种问题,我们痛定思痛,寻找新的方法。这个新的方向是什么?当时我们看了 Google Dapper ( http://research.google.com/pubs/pub36356.html ) 的文章之后,认为基本上就是全链路监控系统。
Dapper 是帮大家构建一套比较完整的端到端的全链路日志采集的规范,是大家可以跟随的一个原则或者准则。它最关键的概念就是 span。
基于 Dapper 原理,有蛮多行业解决方案,比较有名的有淘宝 eagle eye,点评/携程的 CAT,微博的 Watchman,还有听云 Server 等。当时我们也对这些方案做了一些调研,但最终我们还是选择自建平台。
为什么呢?因为唯品会还算有钱,团队的能力也比较强,有蛮多资深的技术人员。除了有钱有之外,更多的是从业务上,从系统层面上去看我们为什么要自建平台。
第一, 系统复杂 。上千个业务域,同时又是异构系统,有很多系统还是 PHP。服务化体系内存在众多的异构系统的,我们要去监控这样的异构系统,显然不能直接用一些业界的方案。
第二, 海量数据 。唯品会峰值每分钟上亿日志量,一天达到上百亿的日志量,这是非常海量的数据。BAT 之外,对比其他公司,唯品会在这个数据量上面也是很高的。对这些海量日志,怎么样端到端对海量数据搜集、计算、存储、检索,这是一个很大的挑战。
第三,我们需要 自建服务化体系的监控 。我们有自建的服务化体系,这个服务化体系要怎么监控?我们的服务端其实需要一些定制的埋点和数据透传机制的,它没有办法说我直接打一个 log,这其实是每个公司有自己的服务化体系,它需要自己去埋点。
第四, 高度可治理 。这个非常重要,因为当你整个摊子铺大了之后,上千个域都接入后,怎么去管理?比如说大促的时候经常遇到我没数据了,为什么没有数据,整个体系是非常复杂的,我怎么样能够快速的知道这个业务为什么没有监控数据了,这就涉及到治理。
另外大促的时候我们要做一些动态的升降级,这些动态的升降级也是依赖比较强的治理能力,需要在生产环境去控制日志的产生。
第五, 快速接入,升级便捷 。因为我们的团队很大,开发人员很多,所以我们的产品要做到让业务用起来很方便,我们需要一些无侵入埋点,快速接入。
同时在我们很复杂的体系当中,需要考虑怎么快速的升级,我不知道大家有没有经历过在一个很庞大的体系当中升级客户端,不管是监控系统的客户端,还是其他的客户端都是很头疼的问题,特别是在业务线众多的大型公司。据说阿里的 dubbo/hsf 的升级其实是非常困难的,我们现在也遇到类似的问题,因此需要提前去考虑这些问题。
第六, 灵活的告警策略和高效告警 。我们需要一个多维度、多监督、多级别、多时效、同时支持告警收敛的告警引擎
第七,还有一个很重要的一点, 监控体系要与公司的很多体系无缝对接 ,我需要满足不同角色的需求,同时要与唯品会的发布、监控、问题跟踪平台无缝对接。
所以正是因为以上种种原因,我们决定自建平台。
先快速给大家介绍一下 Mercury。
-
Mercury 是 唯品会自主研发的应用性能监控和问题跟踪平台
-
基于客户端探针上报应用调用链相关日志
-
基于流式计算和大数据存储和检索技术
-
提供实时和准实时告警,并快速定位根源问题
-
分布式应用调用链路跟踪
-
提供快速有效的数据展现和分析平台
唯品会监控体系
我们的全链路应用监控体系在唯品会处于什么样的位置?
系统层监控我们主要还是 Zabbix,我们在上面做了一些封装,提供更好的交互。
业务层我们有一个产品叫 Telescope,所有的业务监控,包括 PV/UV、定单量、支付成功/失败率等重要业务数据,全部在这个产品上面可以看到。
在应用层,现在 ELK 用得比较多,另外一个产品 SKYHAWK,它其实是 APP 应用端的监控平台。
接下来是 Mercury,唯品会现在所有的应用,所有服务化的应用,所有 PHP/Web 应用,都是通过 Mercury 来做应用层的监控。
为唯品会的监控生态而设计
在说到一些技术之前,我还想强调一点,我们做产品并不是简单地做一个技术,简单地把很多东西搭在一起,我们更多的是要为整个技术生态去设计。我们的产品更多的是为满足整个唯品会的运营、技术运营的需要,为这个生态的需要而设计,所以会涉及到人、流程和技术。
人的话,无非就是开发、监控、运维、运营、管理人员,这些人都会通过我们平台,去察看他们想看的数据。我们的监控系统会发布流程,发布的时候我要监控整个发布的质量,监控流程、故障问题定位、故障问题修复、以及故障回顾,这些其实是整个监控系统需要控制的流程。
技术上,主要包括大数据采集、大数据计算、大数据的存储和分析,这些技术整个形成了我们的监控生态。
Mercury 核心价值
Mercury 的核心价值在于:
-
生产环境 端到端 的性能可视化;
-
展现一个应用的完整的拓扑关系,通过全链路监控系统,我们把整个链路的拓扑展现出来;
-
快速告警,并且定位根源的问题。
这三点我总结下来,是我们这个产品最核心的价值。
海量数据刚才我也提到了,大约如下:
-
1万+ 应用服务器接入
-
接入业务域 500+
-
大促峰值每分钟处理日志 1亿+
-
日均处理日志量 150亿+
-
日均存储日志 5T+
-
日均索引量 1T
这些数据都体现我们的产品,我们是在支撑一个非常海量的数据。
前面提到这么多的统计数据也好,监控流程也好,回头来看一下,Mercury 是怎么去支撑这样的海量数据的?我们的架构是怎么样的?我们可以深入到一些架构的细节,去看怎么去构建一个比较完整的全链路监控系统。
完整的全链路监控系统
一个比较完整的全链路监控系统,通常会包括几个部分。
第一, 数据埋点和采集 ,这个相当重要,其实说白了,数据是整个监控系统最核心的部分,必须有能力快速和正确和方便地采集日志,所以我们在数据埋点和采集上做了很多文章。
第二, 指标计算 。指标计算有好几种方式,一种我可以在客户端做一些计算,通过 agent 上报计算结果,然后到服务端再做加工。还有一种策略是完全在服务端的计算,客户端只做简单的数据采集,所有的指标计算全放在服务端。这二种架构其实都有不同的优缺点。
第三, 指标存储、查询、展现 ,这个也非常重要。算完后的指标放在哪里,这些指标怎么样快速的查询出来,指标怎么样很灵活的展现给我们的运营人员、开发人员。
第四, 调用链的存储、查询、展现 。因为我们是全链路监控系统,调用链怎么存、怎么查、怎么看,这个也是非常重要的。
第五, 告警、问题定位 ,因为你单纯只有查询远远不够,我们需要一个很快速很高效的告警,同时产生告警之后,我们需要很快速的去定位这个告警是什么原因导致的。
第六, 自监控 。我们的体系需要一个自监控的功能,假如我在大促的时候挂了,我需要很快的恢复我们的平台,自监控非常重要。
第七, 治理 。大促之前需要动态地调整日志采样率,或者对日志采集做一些功能降级,例如不采集缓存相关日志。
建设一个完整的全链路监控体系是需要考虑以上这几个过程。
全链路监控技术栈
技术栈,我们的体系有用了很多的技术,Spark 、 Open TSDB 、HBase 、Elastic Search,Kafka,Flume,Python 等,整个技术栈相当之复杂。
系统架构
我们 Mercury 的架构是怎么样的呢,大家来看一下,大致来跟大家描述一下我们整个架构的走法。
先说客户端,应用系统其实是依赖一个 client 这样的探针组件,它其实是一个非常非常高效的数据采集组件,对用户来说是完全透明的,你不需要去做任何的编程,基于字节码织入技术,只需要做一些简单的配置。那么客户端就能够很高效的采集相关日志数据。这些日志是基于一个标准格式的,因此,我们的接入是非常高效的,你不需要关心我采集日志的格式是什么。很多其它的监测系统,比如像 CAT,可能它需要手动埋点,这个时候你可能需要非常关注它的埋点格式是什么。我们是不需要关心埋点格式的。
我们的日志数据是不会直接传到后端的,它首先落地在客户端的磁盘上面。
接着我们有一个 flume agent,基于 Flume,我们做了一个插件,这个插件会非常高效地采集我们客户端的日志。
当我们搜集到了日志之后,它就很高效地把日志推到 Kafka 集群里面。我们要保证它的日志是非常精简的,因为唯品会有多个 IDC 机房,跨机房低带宽占用是非常重要的。这个上面我们做了一些基于 avro 的数据序列化的优化,序列化之后带宽基本上降了一倍左右。 我们没有用压缩,因为压缩会导致客户端的消耗比较大。
当这些数据来到 Kafka 集群,后面有几个集群,首先第一个是 Spark 集群,这是一个非常重要的数据流式计算的集群,我们通过 Spark 做指标计算,让所有的日志都通过 Spark 很快速的算下来的,基本上延时在两分钟左右。考虑到端到端数据上报的时效性,其中只有一到两分钟是计算的延时。
指标算完之后,一条线到 OpenTSDB,如果做过监控的同学对这个应该是蛮熟的,相对来说也是比较成熟,比较简单的基于 HBase 的一个时间序列的框架。
另外一条路,通过 flume 写到 HBase 里,因为大家看到其实我们有调用链,那我们要对调用链做很快速的检索,我们以前是直接把调用链日志写到 HBase 里,但是有一个头疼的问题是它的写入很快,通过 rowkey 一键查询也很快,但是如果说你需要做一些比较复杂的查询,HBase 是不支持二级索引查询的,你必须自己在它之上构建二级索引。
原来我们直接设计了一个索引表,但在海量数据下,查询起来非常之慢,所以我们后来用 ElasticSearch,首先日志数据送到 Kafka 后会被消费两次,一条路是直接送到 ElasticSearch 里建索引。另外一条路是调用链日志直接落地到 HBase。
查询的时候,首先到 ES 里根据查询条件定位到 trace id,接着拿着 trace ID 到HBase 里一键把所有的端到端的请求都拉出来,所以通过一个 Trace ID 就能一下子把作为的调用链全拉出来,因此这个设计是非常轻巧的。
整个体系还有很重要的是两个告警引擎,它分为实时秒级告警和一个指标告警。实时秒级告警更多的是对严重问题的告警,比如大量的服务超时,或者服务熔断。这个时候我们整个体系会上报一些事件,会有一些熔断事件,我们会预埋在我们的服务框架里面,一旦遇到这种问题的时会立刻上报。我们的上一级告警框架,会立刻检测到这个 alarm,然后做实时告警。
同时我们会对这些异常做一些收敛,假设瞬间好几百台或者上千台的主机都产生了异常,那整个异常都会一下子上报服务端,这个时候你会在服务端产生大量的报警,所以我们秒级告警会做一些收敛,会做一个特征值的设定,我会在三十秒或者十秒,这个时间窗口你可以定制,比如我十秒的窗口,我先对一些有特制的异常做一些签名,缓存到内存里面,如果在一段时间之内,上报的异常匹配了特征值,则不会做重复告警。
当这个时间窗口过去之后,这个特征值就会失效,下一次再进来的时候,我们会重新构建这个特征值。我们用这样的一个方法实现告警收敛。
下面这个是指标告警,它是一个周期性的告警任务,它会根据我的告警规则设定,定时扫描指标数据库做一个周期性的告警。比如连续三分钟,响应时间大于 30。毫秒就告警,类似这样的告警我们通过周期性的告警规则来设定。
在后面还有 Dashboard 和告警跟踪系统。上面也提到,我们是为整个唯品会的监控生态而设计,这个告警跟踪系统会把我们所有的告警聚合到一起。告警看板的作用是说我立刻能在大盘上看到所有的告警,但是这些问题是不是已经处理完了,我们是需要一个比较好的跟踪平台去跟踪的,所以我们设计了这样的跟踪系统。它是长时间运行的,开发人员每天会上去看。比如我昨天有些告警,这些告警有没有处理完,都会在这个系统当中呈现。
调用链
整个体系还是蛮庞大的,接下来跟大家说一下我们整个体系最核心的调用链。
什么是调用链,大家可能第一次接触全链路系统,可能对调用链的概念并不一定熟。你可以认为它是一个串联端到端请求的链路,在这个链路上面我们会看到非常丰富的信息。
调用参数
我们在调用链上面我们去关联说这个调用的参数是什么,我的出参入参可以在这个调用链上面。
业务关键字
这里我需要稍微解释一下什么叫业务关键字。很多时候想象一下这样一个场景,有一个用户下单支付失败了,这个时候我们怎么样知道,为什么这个支付失败了,到底是支付整个链路上面哪里出了问题,这个是需要我们还原现场。
如果有一个系统它能够帮你还原这次调用,到底哪一步出了问题,所以你需要去反查,你需要拿着一些业务上面预先埋的点,比如说定单号或者用户ID,通过这些信息我能够反查到当时用户下单操作的那次调用,再去看那次调用到底哪里出了问题。所以业务关键字是非常重要的。
我们要求核心的业务系统要通过我们的 API 去埋它的关键字,把它的用户信息、订单号,或者说我们一些很关键的业务字段,都预先的埋到我们的调用链上面,出了问题之后就可以反查。
调用耗时
这个应该很清楚。事件,因为有的时候我们要调用的时候,我们可以把一些这次调用产生的事件关联在一起,我们也可以通过事件去反查当时的调用。异常日志这个信息非常重要,因为当你出了 500 的时候,一定会有异常产生,这个时候我就要通过这次调用去关联这个异常,然后通过这个异常信息我就知道产生问题的是什么。
调用结果
调用结果,成功失败。
调用链总结
所以整个调用链的信息还是蛮丰富的,那么调用链最核心的是什么?核心的是 每个请求都会生成一个全局唯一的 TraceID 和 SpanID ,它都会端到端的透传到上下游所有的节点。每一个请求 TraceID 都要透传上去,从最上游的移动端一直到最下面的数据库, 通过这个 TraceID 我们将不同系统的孤立的调用日志和异常日志串联在一起 ,同时 通过 SpanID 可以表达节点的父子关系 。
下面我为大家来说一说我们每一个组件当中的一些非常重要的设计原则,
1. 客户端 - 举重若轻
首先从客户端讲起,客户端我把它形容为举重若轻,你需要有一个很轻量级的客户端,但它同时又能够承担很大的流量,怎么能够做到这一点?我觉得很关键有以下几点需要去注意。
高效率
高效率更多的是对开发人员来说,我要让他们很快能够用我们的东西。我们现在是支持 Java 和 PHP,高效率主要体现在:
-
Java 采用 AOP 埋点,不管是一个 Java 调用、其他的调用还是服务化调用,我们都会拦截所有的请求,植入我们的逻辑,包括透传。
-
PHP 也有自己的框架,所以在 PHP 里面也进行预埋,对 PHP 的应用逻辑而言也是透明的。
-
日志格式是标准的,要不然日志框架没有办法很快的去监控我们的系统,用户是没有感知的。
高性能
在数据采集端其实负载很低,客户端对应用的影响控制在 5% 以内。为什么能够有比较高的性能?因为我们的客户端会构建一个异步日志队列。同时,会良好的管理内存和 CPU 的使用。
我们通过 AVRO 序列化传输,可以节省一半的带宽。
通过设计日志数据格式,避免冗余数据。整个客户端我们先把数据打到磁盘上面,磁盘 IO 比较高效。这个时候 IO 性能是比较重要的,如果我的日志格式是相当冗余的,会导致 IO 上升。所以我会做一些比较精巧的设计,让整个日志相对来说比较小。
高可治理
客户端的可治理也非常重要,在高可治理上面我们做了这些事情:
第一集成我们的配置中心。唯品会有一个比较重要的组件,就是配置中心,它承担了所有的业务配置、系统配置、以及服务化的动态发现。我们现在服务治理也放在这个配置中心里面,包括服务的一些路由策略。当然在配置中心做了一些分群,让整个管理比较可控。
通过这个配置中心我可以动态下发很多的指令,采样率等等都是可以被动态的降级,比如 HTTP、OSP、SQL 等也可以关闭,出问题时,一键关闭这个客户端的日志采集。
采样率可以动态的调整,我不知道大家对这个采样是怎么理解的。采样是蛮有讲究,采样不是说我这个域要设一个采样率,那个域要设一个采样率,在全链路监控系统上面,采样率必须要在调用源头设置,需要在最上游去设置采样率,然后采样率会透传到下游所有的节点上。每个服务一看上游给过来的,告诉说要采样我就用它。所以整个采样率是完全跟着最上游的节点来看,每个模块不能随便的去设采样率。这个也是非常关键的一点,想明白这个就非常简单了。
同时除了源头采样之外,我们也支持域级的采样,虽然源头设了采样率,但是我不想跟随,我就想自己设一个采样率,也是可以支持的。这样有一个什么后果?假如说我源头设一个采样率,你自己也设了一个,后果就是出了问题的时候,我去查我的调用链信息,如果不跟随相同策略,有可能当中的链路会断掉。
客户端会上报客户端的版本,这个也是相当的重要,因为在一个比较大的体系当中,版本的规划与升级是相当重要,这个我们其实也踩了很多坑,因为业务在飞速发展,组件在飞速发展,这个时候如果需要考虑升级,就需要考虑线上到底有哪些版本,有哪些域需要推动他们去升级,我不知道对方需要什么版本,因为一段时间之内,他可能会出很多个版本,最多的时候我们有五、六个版本在线上跑,当出新版本的时候,你需要推动升级,有上千个域,你不知道有谁在用你的版本。所以我们会把所有的版本全上报到我们服务端,通过我们的治理平台,会很快看到我每个域的版本。
敏感信息脱敏,这个也是非常重要的,因为我们采集的信息相当丰富,所有的出参入参都会采集,会有很多敏感的信息,帐务信息、用户的登陆信息,这些都是需要脱敏的。
可升级性,轻量级客户端,还是跟上面这个版本有关,我们的客户端要能够支持我持续升级,我不能说每个版本它的一直工作很好,我一改以后功能都需要去升级,所以可升级是相当重要的。
上面是建设客户端需要注意的几点。
2. 服务端 - 海纳百川
下面是服务端,服务端我们形容它是海纳百川,因为整个服务端的数据是海量的,每分钟上亿的峰值量,所以站在服务端角度我们要考虑这些事情:
流式计算
首先是流式计算,Spark 是一个主流的流式计算平台,我们要关注几点,这里我不想去讲 Spark 的架构,更多的是我们怎么样去用好它。
第一,Kafka 端建立多 Topic,之前老的方案我们会只建立 2 个 Topic,对 Spark 内存开销比较大。因为 Spark 需要消费这 2 个 Topic,因为不同的指标其实需要的数据量是不一样的,需要的数据类型是不一样的,如果你 Topic 数量比较少,意味着我的 Spark 需要消费一个很大的池子,然后把池子里的数据做一个筛选过滤再去做指标计算,这个对 Spark 的内存需要是相当大的。
举个例子,我的池子里面有一百条鱼,我要算这一百条鱼,不同的鱼有不同的品种。假如我只算某一种鱼类品种,我只需要把这种鱼的类型挑出来再去算。如果是一百条还好,如果是一亿条这个量是非常之大的,所以我们需要在客户端的时候,首先把这些不同类型的日志分类,接着分发到不同的 Topic 上面,然后 Spark 按照不同的计算去消费不同的 Topic,这样就不用一亿条鱼全部放到里面去算了。我只需要用不同的池子去算不同的指标。
第二,Wall Time Or Event Time,这是两种不同计算的策略。
第一种是只算当前的时间,我根据当前的时间来算,根据当前的时间来算有一个重要的缺陷,由于日志上报其实是有延时的,有些日志可能受限于客户端,可能网络的问题,或者客户端有失败的调用,或者说客户端和服务端连接的问题,或者客户端的时间有问题,都会导致我的日志没有很及时的跟上,没有很及时的上报到服务端,所以通过 wall time 方式就会过滤到部分日志,精准度就不高。
第二种是 Event Time,是根据整个日志上报时间来的,不是根据当前时间来的。
第三,这里有个很重要的点,即我们现在基于 二阶段计算模式 ,能够保证整个 Spark 计算的精准性。大家知道 Spark 其实是一个批处理,更多的是一个批量的去处理日志,我们目前的策略会每隔 20 秒去消费这 20 秒的数据,然后把这 20 秒的数据算完之后推到 Kafka 里面,我先不给一个最终结果,后面还有另外一个集群,它会再去计算这 20 秒的数据,再把它聚合成一分钟的数据。
这样的好处是什么?因为二阶段计算非常重要,它能够容忍 30 分钟的延迟,假设网络有延时,或者客户端有问题日志上报很慢,这时二阶段就能够包容这样的错误,它会计算 30 分钟以前的数据,经过聚合以后,通过一阶段的计算,把这个数据量降到一个很低的程度,这个时候二阶段可以重新去算这 30 分钟的数据。这个架构是非常之轻巧,使得我们计算的精准性相当之高。
第四,我们会记录数据回放 Checkpoint,支持容错,这是流式计算很重要的一点。假设 Spark 垮了,需要从头找当时消费的 Checkpoint 去做一些容错,我会定时的把这个 checkpoint 存下来,需要做数据回放的时候,我们就会通过它重新去拉数据。
第五,数据流控。为了保证我们的流式计算,我们会在 Spark 端做一些流控。
第六,参数调优。内核参数其实也是相当重要,针对流式计算非常大的痛点就是,假设你忽然有一个非常大的 STW(Stop the World) GC,整个流式计算就会产生延时,产生延时的一个最大后果就是所有的指标趋势图,都会看到一个波谷,因为产生延时之后,整个指标就已经不准确了。
之前在没有对内核参数做调整之前,经常会出现峰值高峰时,忽然监控打电话来说,为什么某个地方没数据了,过了五分钟又有了。这个时候就是因为系统在做 GC,或者有内存的换页,或者有内存的回收。
所以我们对这块做一些性能调优,让整个内存的回收是比较平缓的。大家可以去关注一下 Linux numa 架构,它对这个内存的管理。所以我们在这上面做了一些调优,使得 Linux 内核对于内存的管理做得相对比较平缓,不至于非常激进,比如内存一旦不足就立刻做 swap 或者内存页回收,导致整个系统 CPU 消耗很高,最终导致计算超时。
海量存储
基于 OpenTSDB 保存全量指标,我们其实存的是全量日志,针对于指标你是可以查询历史上所有的数据点。
对于 TSDB,优化整个查询性能很重要的两点,第一点是控制 Cardinality 的大小,要保证在设计我们指标的时候,让整个 cardinality 尽量的小,否则从上亿条里面去做一个指标查询是很慢的。
怎么样让它变小?很重要的一点就是通过 Shift to Metric 去设计这个 Metric Name。所以当我把这个这个 Metric Name 作为一个指标名的时候,我就能把我当前的指标变小,可能我的指标会膨胀,可能我一个指标变成十个指标了,但是我单纯去指标的时候,就会变的很快。
高速查询
最后是高速查询,我们通过 ElasticSearch 建立索引。ElasticSearch 怎么分布,怎么部署,怎么建立索引?我们现在是按日,而且按域建立索引,每天每一个业务域,它会去建立一个索引,而且针对于不同的重要的业务域,我的索引机器也不一样,有些很重要的业务域可能给它多一点资源。
Trace Log 进入 HBase,TraceId 作为 RowKey,一行保存一条调用链。
先查 ElasticSearch,再一键定位调用链。
告警平台建设
告警平台的建设,也是 Mercury 里面比较核心的一个功能。
首先是秒级告警。我们会端到端的延时30秒内,对异常日志和异常事件告警,同时对告警做一些收敛。
指标告警延时在两分钟内。我们的告警机做了动态分配,我们后面会有若干台,现在大概有五台告警机,每台告警机会分配一些资源,这些资源都是可以动态分配的,也是基于ZK做的一个告警机的动态的注册,动态资源的分配。所有的告警都会推到我们的告警大盘上面。
告警策略也是相当重要的,我们做了一些设计,我会分离我的告警策略和告警分派,你先去指定策略,然后再把策略分派给不同的业务。多维度、多监督、多级别告警规则配置,针对业务域、针对主机、针对API,各种不同的告警都会体现在我们的告警策略上面,所以我们的告警策略场景覆盖率是相当高的。
再说一下多层告警的重要性。在我们的运营过程当中,要对不同的告警做不同的分类。我们现在其实会会警告和告警,产生警告的时候,通常来说可能我们的监控人员不会一下子看这个警告的问题。当这个警告问题上升到告警的时候,这些人才会扑上去。这个策略是蛮重要的,如果说你一下子产生告警,每个监控人员都一直要关注这些告警那是相当痛苦的。所以我们对告警会分不同的池子,哪些池子是你不需要很快的去看,哪些池子是需要你立刻去看的。
监控质量保障——笃定泰山
功能都有了,那我们还缺什么?我们要保障监控质量。
监控数据的质量是非常重要的,不能在大促的时候,监控先出问题。做过监控的人也知道,监控系统最麻烦的就是大促的时候,其他的业务域没出问题,你自己先出了问题。监控数据量很大,而且对于很多业务团队来说,监控系统的重要性不一定排在第一,所以它给到你的资源相对来说也是比较少的。所以经常会遇到说我的监控质量下降了。
我们可以通过下面的方式去保障我们的监控质量:
第一,在系统层,我们的监控系统的组件都会接入 Zabbix,对通常系统层面上的指标做一些监控,特别对机器的指标也要做一些监控。我们也会监控跨机房的网络流量,因为这个流量也是非常宝贵的。
第二,对客户端,我们会监控客户端的日志落盘状态。客户端有问题日志没有正常落盘,落盘之后是不是有丢失。agent 发送的日志的状态,是不是有异常,是不是有堆积,channel size 就能够帮你监控。
第三,组件的监控。我们会对 Kafka topic 做一个监控,持续观察 topic 的入口流量。很重要的一点,对 lag 要进行监控,它能让你很快的知道下游的消费是不是存在一些延迟。因为我们很多的问题都是通过监控 lag 能够快速的发现的,所以对 lag 的监控和告警是非常关键的。还有 Flume 的指标、Channel Size,以及对 HBase 也会做很多的监控。
系统界面展示
讲了这么多技术的东西,让大家轻松一下,看几个我们的画面。
这是我们的入口,我们会提供若干这样的功能,从业务监控、大盘、策略配置、调用链查询、主机系统查询,组件版本查询等等。进入这个区域之后我们会展现更多的信息,曲线、服务信息、日常失败、系统信息等等,都会在这个平台上面。
这个是我截的一个调用链,相对来说还比较简单,它只跨三四个域。响应的时间,察看细节,你可以看到调用链的细节信息。
这是一个告警策略的指定,大家可以看到我们会先去设计告警策略,然后在这些告警分配出去,告警很简单,比如说连续五分钟环比请求失败率大于昨天的三倍。告警策略我可以组合,做一个规则组,当规则组同时满足的时候我才告警。
这个是对业务域治理的功能,我们会每天去看哪些业务域什么时候接入的,负责人是谁,我的告警策略有没有分配,我的告警机有没有分配。如果出了问题,我会立刻排查标签,是异常、正常,还是警告。所以我们的运营人员一看都知道说,你这个运营可能是异常的接入,他可以直接去找那个负责人做后续的跟进。
展望未来,智能化告警,开放平台,打造我们唯品会后端的 SaaS 品牌。所谓 SaaS 就是说从客户端你可以把你的数据推上来,推之后我们在这个平台上面帮你做指标的展现。这个指标展现,可能涉及到你可以自定义很多的报表,同时我们可以帮你对所有指标都做告警,你不用关心后面的时间是什么样的,你只要把数据送上来就可以了。包括容器化的监管,离线分析平台,最后是对整个系统监控数据做结合。这些就是我们未来要做的一些事情。
Q&A
提问:你们应该对有对 client 进行改造的吧?
姚捷:对,我们有自己的插件。
提问:需要到生产的机器上去安装这个东西?
姚捷:那肯定的,但是这个对业务系统不关心,它不是业务的部分,完全是部署在生产机上的 lib 而已,完全是运维去推动就可以。
提问:序列化直接在 client 做的?有没有遇到机器的 CPU 过高?
姚捷:没有遇到。我们对整个客户端做过蛮多的压力测试,单机的峰值量是蛮高的,我们一个域可能入口流量是五六千,但是五六千的 QPS 后面还会访问很多的下游域,日志你可以认为单机有五六万的 QPS。在现场我们做过一些压测,CPU 过关,但是出现过 gc 频繁的情况,因为对于 gc 的参数,优化也是蛮重要的,因为毕竟它会跟你争抢增援,所以对 gc 的优化也是非常重要的,避免它去争抢资源。
提问:刚才说在客户端高性能的时候测试了一个并发队列,一个是无锁的,一个是有锁的,那我想知道如果用一个无锁队列的话,因为它在没有的时候,获取的是 null,那您如何做到触发式?
姚捷:无锁队列它的优点在于,它是类似于有两个门,我的日志进入我的前门和后门的时候,有一个比较好的技术,能够保证一个环形。但是这个队列要足够的长足够大,当我日志量打满我整个环形的时候,还是会包不住。所以基于这个特性,因为我们整个客户端要做到容错,当落盘出问题的时候,client 要保证不影响业务。所以基于这个前提,我们没有用无锁,因为你用无锁之后,首先这个队列要求比较大,这个环形如果比较大的话,性能比较好。如果无锁比较小,所有的线程打满这个环之后,落盘出了问题,这个业务上是不能接受的,宁可丢弃日志,也不能 fail,这个是我们在业务端的策略。