编辑推荐: |
本文来源infoq,文章主要对
Focus 的设计思路和关键实现进行剖析,详细介绍了存储设计、架构设计以及数据结构和处理设计等。 |
|
应用监控是多数互联网公司最重要的基础设施之一,其意义不仅在于可以帮助开发人员应对分布式环境下的
Trouble Shooting 和性能管理难题,更是系统可用性的第一步。Focus 是由随手记研发的统一应用监控平台,承载了随手旗下随手记、卡牛两款产品数百个服务的应用监控任务。本文将对
Focus 的设计思路和关键实现进行剖析。
监控的体系
“监控 " 是一个宽泛的概念,代表了很大的一块领域,有很多独立的系统在其中发挥作用。在介绍具体内容之前,我想先阐述一下通常互联网公司的监控的体系是什么样的。
一个完整的监控体系通常至少包含以下三个层次的监控:
系统层监控:用户为基础运维人员,面向机器视角,关注机器的 CPU、磁盘、IO 等。
应用层监控:用户为开发人员或 DevOps 人员, 面向应用视角,关注应用程序的可用性,不仅要求能够发现故障,还要提供线上故障原因排查和性能调优。
业务层监控:用户为运营人员,面向业务视角,关注业务的关键指标如下单率,转化率。对可视化有较高要求。
在实际运用中,运营和开发人员都会关注业务指标,如果业务指标异常,则首先 check 应用监控了解是否系统原因导致,如不是则可能是外部原因如营销活动、渠道问题等。开发人员在日常
DevOps 活动中则重度依赖应用监控,任何动作都需要确保对线上的影响是可控的。若发生问题时问题根因指向系统层面,则运维
check 系统监控是否存在系统级问题,如磁盘满,网络抖动。
以上就是一个互联网公司的典型监控体系,本文介绍的 Focus 系统是一个应用层监控系统,专注于为开发人员提供线上应用的故障排查与性能管理能力。
诞生背景
开源社区在监控体系内各方向上均有成熟的产品贡献。和大多数公司一样,随手记最也选择基于开源产品来组建应用监控系统,并摸索实践了
1 年多的时间。直到架构发展到如下阶段:
在这个阶段我们实际搭建了三个子系统作用于日志、调用链和指标的处理,并且通过二次开发的方式对其进行了很多适用性和易用性改造,基本实现了开发人员的常见需求。但我们的问题还没有解决完,当我们想要更进一步的时候我们遇到的问题是:
接入和使用横跨多个系统,无法互联互通,效率低;
无法形成统一连贯的排障步骤,信息无法充分利用;
无法继续演化和扩展,一些问题和需求需要深度整合开源产品;
架构太复杂,由十几个组件拼装而成,系统很脆弱。团队维护工作繁重。
因此我们在 2017 年决定使用平台化手段解决上述问题,开始着手建设统一监控平台。力求整合资源,统一处理,用一套系统解决应用监控问题。Focus
就是为满足上述目的开发的监控平台,目前承载着“随手记”、“卡牛”两个产品近千个核心服务的应用监控任务。
设计理念
监控系统本质是一个采集和分析系统,设计上则要遵循分析指导采集的思路。应用监控的难点就在于不仅要回答“有没有问题”,还要回答“是什么原因”。我认为要回答好这两个问题,至少需要以下
3 个维度的信息做支持:
Transaction 事务 :一个应用系统必然是为了完成某样事务存在的,那么它的事务完成的如何?
例如对请求的响应是否成功,每个环节性能快慢?可以说事务执行情况代表了应用的可用性。
Event 事件:应用内部发生了什么事情? 例如是否有异常发生?是否发生过主从切换?事件溯源为解决问题提供重要的细节信息。
Stats 状态 :应用内部的状态如何? 例如当前队列的长度? 线程池空闲线程数?一共处理了多少数据?
状态的观测和预测在发现问题方面非常有用。
Focus 不仅要满足对这三种监控信息进行的采集和分析,还要利用它们之间的配合来组成一个完善的故障排查逻辑。通过数据来支撑工程师做出判断,逐步收敛和排除噪声最终过滤出根因,这是
Focus 系统能够高效排障的思路。
实现方面,平台化的核心优势是可以进行多种数据的联合分析与展现。在出报表上做到原先单一系统做不出的报表,提供更有力的决策数据;在报表的规划上则可以做完整排查逻辑并衔接成清晰的排查步骤,提升故障排查能力。
举例来说,当用户接到一个告警,在系统中的操作路径是:
检查依赖拓扑报表,确定依赖和被依赖的服务正常,问题是自身问题;
跳转到状态报表,发现应用平均响应时间异常,划选异常时间段;
跳转到事务分析报表,通过响应排行发现该时间内 A 接口响应时间严重变慢;
在 A 接口慢事务 Top 排行中选中一条具体事务,观察该事务的瀑布图,发现是数据库查询方法慢;
跳转到该处日志,发现是意料外参数导致的全表查询。
整个排查过程思路清晰且每个跳转均提供有力的数据支撑,没有多余噪音。该操作逻辑的背后则是多维数据的相互协作。
数据结构和处理设计
Focus 使用以下三种数据结构来承载对事务、事件和状态的落地:
事务观测:基于 Google Dapper 理念的 Span 数据结构。 包含耗时和失败等事务状态信息,通过
Tracing ID 支持分布式事务。
事件观测 :事件日志,KV 数据结构,可以附加任意多的额外信息。如果是事务内发生的事件则可以通过
Tracing ID 追溯到。
状态观测:支持多维度和多值的时间序列数据结构。
在数据处理流程方面,Focus 设计为一个全量采集系统,但不支持全量数据检索。我认为全量数据检索是导致监控系统臃肿复杂的重要原因。在监控场景中,用户需要有代表性的数据以支持决策,而不是海量原始数据的直接检索。因此
Focus 从一开始就放弃了做全量数据存储的念头,只保留典型样本数据即可。这样的设计还降低了流量敏感性,在
11.11、除夕红包等大型活动下,业务流量突然增大不会导致系统存储压力有明显的变化。不过 Focus
保留了原数据实时供数能力 (Kafka)。在随手记内部,额外的分析需求是由单独的大数据系统负责的。
最终 Focus 的内部数据处理流程设计如下:
对目标的把握和清晰的设计思路让 Focus 面对海量数据流量时可以保持简单,高效,低成本。
架构设计
整体架构设计上,Focus 主要满足几个特点:
简单。Simple is powerful ,简洁架构让问题变的易于处理。系统一共只有 3 个组件,运行时额外依赖
Kafka 和 ElasticSearch 集群分别作为数据的 Hub 和 Store。原始数据由采集组件搜集并发送给
Hub,由 Hub 聚集后按照分区逻辑分发到对应的计算组件做实时处理或异常检测,处理结果再统一存储到存储组件中,最后由控制组件查询使用。
高吞吐。监控平台目前每天处理数百亿消息,整个处理过程不能存在瓶颈。数据处理主流程增量运算,能异步的地方都采用异步设计。另外整个架构是可扩展的,服务端以无状态集群的方式工作,可以通过投入机器的方式应对更大的流量。
实时性。监控数据时效性明显,越实时的消息越富有价值,因此系统应该尽量快的出结果。 Focus 是一个准实时处理系统,数据处理过程可完全不落盘,设计上尽量考虑时间因素,目前大部分告警可在
1 分钟左右发出。
高可用和自愈。监控平台是用来排查故障的,所以自身必须是高可用的且不易受到故障影响的系统。Focus
架构中没有单点问题,且服务故障时可自动剔除出故障的服务。
故障容忍。当监控系统出现问题,不能影响业务应用。Focus 存储被设计为次要组件,存储挂掉不影响实时增量统计和告警,服务端全挂掉不影响目标应用。
部署简单。Focus 设计为可以开箱即用,只需要一个简单的命令就可以启动并在集群中发挥作用,不需要复杂的配置和学习。这样面对多个环境部署时就不那么痛苦。另外开发时也很容易在本机启动起来。
最后整个架构是演化而来的。2015 年的时候我们只做到了 Version 1.0 当中的组件 (编码为
1 的组件)。 在后续几年的演化中逐步形成了现在的架构,系统也由一个 ELK 型的日志系统演化为了一个完善的
APM 系统。
服务端设计
Focus 服务端本质上是一个流计算系统。而如何化解流计算系统固有的挑战是主要设计问题。我主要讲以下几个关键问题的决策:
数据状态问题。分布式服务的状态处理是设计中的重点,我们通过简单的数据分区处理方式,让同一个维度的数据进入同一个
Partition。这样计算服务就可以无状态,无状态的好处是可以任意扩展,且避免了服务间的通信等一系列困难。但是分区模式会带来热点问题,这一块我们目前通过更细粒度的分区来缓解。
异构数据处理。Focus 的数据处理要支持 3 种不同的数据结构,即需要 3 套 Pipeline
来处理。 而 Focus 的服务端是同构设计的,这样的设计更利于运维的简化,因为同构服务的集群可以被抽象为一个服务去管理。为了做到这一点,每个实例都会同时运行截然不同的计算任务,会导致算力不均。为此我们设计了一个算力调度模块,可以动态或手动调整算力分配。
故障自愈。流计算也就是实时增量计算,它不像离线计算,Job 出错了大不了再跑一次。增量运算对可靠性提出了更高的要求。在故障处理设计中,Focus
依赖 Kafka Rebalance 机制来实现故障转移。当某个实例出现故障,Kafka 会将原本由其负责运算的数据
Rebalance 到另一个正常的实例从而保证消费不会中断。
流计算框架选型。在上述约束下,最终我们发现 Kafka Streams 非常适合我们的场景,尤其是
Kafka Streams 作为一个 Library 而不是 Platform 和我们的设计非常匹配。此外它还帮我们做到了
Exactly-Once 语义,以及提供了基本的如 Window 和回填机制等工具,使得开发 Pipeline
的过程非常愉快,代码变的非常简单。
齐全度。最后关于齐全度的问题,我们的流计算基本机制还是靠 Window。 那么就存在有的数据早到有的数据晚到的情况,如果数据还没到齐全,这个
Window 就关闭了推去计算了,就可能漏算从而造成误告警。 这部分我们目前使用的还是简单粗暴的等待方案,Window
会等待 3-5 个周期,如果超过这个等待周期还不来,则等同于发生了问题。
存储设计
由于 Focus 同时处理 3 种异构数据,并且每一种数据结构的吞吐量都非常大。在早期设计中为了接纳这些数据存储需求,使用了不同的针对性数据库产品。最多的时候平台需要依赖
4 种不同的数据库产品才可以跑起来。这带来了很大的存储方面的复杂性,于是我们开始想办法着手简化。
存储的问题在于:需要用一个数据库支持三种数据结构的高性能存储。考察了一圈,我们决定使用 ElasticSearch
作为低层引擎。理由是 ElasticSearch 可以很好的存储 Span 和 Log 数据,并拥有胜任海量时间序列存储和检索的基础特性:
高效的倒排索引机制。
列存储能力,并且可以对列存储进行压缩。
强大的聚合能力和非物化视图的特性,可塑性高。
扩展非常简单,还支持并行运算。
在合理的优化下,速度很快。
依靠 ElasticSearch 的强大可塑性,我们在其上设计了一个装饰层。 通过装饰层,ElasticSearch
将作为低层存储引擎按需提供以下几种形态的服务:
先进高效的时间序列数据库。
专为 Trace 存储和检索设计的链路数据库。
日志全文检索库和存储库。
以时间序列为例,上图为列举的一些大块的优化点,在装饰服务的针对优化下,最终我们做到了压缩比 10:1
的时序存储,单节点~1w 读和~12w 写的性能,90% 的聚合查询都可以在 2 秒内返回结果, 并且支持复杂聚合查询。
这个结果在我们的场景中相比 InfluxDB 也是毫不逊色的。
客户端设计
Focus 客户端会一次性把全部需要的信息采集到,包括 Span、Log、Metric 和一些 Metadata。是一个一站式采集客户端。
在接入方面提供针对事务、事件、状态的三套埋点 Low-Level API。开发人员可以按需埋点满足个性化需求。在
Low-Level API 的基础上,Focus 提供常见中间件的预埋点包,大多数应用的接入都只需要引包即可,甚至可以不需要配置文件。
在设计上我们让客户端尽量的简单,只做好采集这一件事情就可以了。因此放弃了一些有诱惑力的想法,包括在客户端进行聚合、配置下达、字节码修改等。事实证明这样带来了很多好处:客户端更不易出错且设计稳定不易变化。出错和易变是客户最不愿意接受的;整个客户端的逻辑很少且容易理解,在开发跨语言客户端和用户自行扩展时都非常的容易;另外性能也很容易做到很高。
在实现上客户端的整个处理步骤都是异步的,减轻对业务的影响。 优化方面手段主要是针对队列和序列化的优化、对消费线程唤醒的控制、以及严格控制内存和对象的使用,防止因为监控导致
GC。
总结
事实上监控系统内的设计决策远不止这些,篇幅所限就不全部介绍了。最后我总结了一些在进行监控系统设计时的心得可以供大家参考:
Simple is powerful 少就是多。做设计抉择时,只有一个核心评判标准:哪个方案更简单。
不要让客户端做太多事情。我们曾经维护了一个功能强大而智能化的客户端,直到不得不下决心重做。Focus
版本越高的客户端功能反而越少。
考虑全量存储的必要性。存储很容易成为系统的瓶颈,我们尝试在全量存储上做优化,后来发现 90% 的信息都没人看,于是我们转变了思路。
利用开源工具来实现你不感兴趣的部分,只做感兴趣的那部分就行了。 Focus 把很多枯燥困难的工作都甩给
Kafka 和 ES 来解决了:) 。
为目标设计。先了解用户想看什么并为此设计报表,然后为了实现该报表而反推数据处理和采集设计。错误的思路:采集一堆数据再进行数据挖掘最后看看能出什么报表。
不追求完美。勇于把不完美的东西发布给用户用,吐槽是必然的,但有用户才有演化的方向。
最后我想说 Focus 仍然在不断的迭代中,很多设计取舍也和公司规模和具体场景有很大关系,所谓抛砖引玉,欢迎大家和我们交流。 |