Focus 是由随手记研发的统一监控平台,承载了随手旗下随手记、卡牛两款产品近千个服务的应用监控任务。本文将对其设计思路和关键实现进行剖析。
(本文根据2018年10月张越在QCon上海站的演讲整理而成,有一定的补充和删减。)
开源社区在监控领域内各方向上均有成熟的产品贡献。和大多数公司一样,随手记最初也选择基于开源产品来组建自己的应用监控体系,并摸索实践了1年多的时间。直到架构发展到如下阶段:
在这个阶段我们实际搭建了三个子系统作用于日志、调用链和指标的处理,并且通过二次开发的方式对其进行了很多适用性和易用性改造,基本实现了开发人员的常见需求。但我们的问题还没有解决完,当我们想要更进一步的时候我们遇到的问题是:
接入和使用横跨多个系统,无法互联互通,效率低
无法形成统一连贯的排障步骤,信息无法充分利用
无法继续演化和扩展,一些问题和需求需要深度整合开源产品
架构太复杂,由十几个组件拼装而成,系统很脆弱。团队维护工作繁重
因此我们在2017年决定使用平台化手段解决上述问题,开始着手建设统一监控平台。力求整合资源,统一处理,用一套系统解决应用监控问题。Focus 就是为满足上述目的开发的监控平台,目前承载着“随手记”、“卡牛”两个产品近千个核心服务的应用监控任务。
监控系统本质是一个采集和分析系统,设计上则要遵循分析指导采集的思路。应用监控的难点就在于不仅要回答“有没有问题”,还要回答“是什么原因”。我认为要回答好这两个问题,至少需要以下3个维度的信息做支持:
Transaction 事务 :一个应用系统必然是为了完成某样事务存在的,那么它的事务完成的如何?例如对请求的响应是否成功,每个环节性能快慢?可以说事务执行情况代表了应用的可用性。
Event 事件:应用内部发生了什么事情? 例如是否有异常发生?是否发生过主从切换?事件溯源为解决问题提供重要的细节信息。
Stats 状态 :应用内部的状态如何? 例如当前队列的长度? 线程池空闲线程数?一共处理了多少数据?状态的观测和预测在发现问题方面非常有用。
Focus不仅要满足对这三种监控信息进行的采集和分析,还要利用它们之间的配合来组成一个完善的故障排查逻辑。通过数据来支撑工程师做出判断,逐步收敛和排除噪声最终过滤出根因,这是Focus系统能够高效排障的思路。
实现方面,平台化的核心优势是可以进行多种数据的联合分析与展现。在出报表上做到原先单一系统做不出的报表,提供更有力的决策数据;在报表的规划上则可以做完整排查逻辑并衔接成清晰的排查步骤,提升故障排查能力。
举例来说,当用户接到一个告警,在系统中的操作路径是:
检查依赖拓扑报表,确定依赖和被依赖的服务正常,问题是自身问题
跳转到状态报表,发现应用平均响应时间异常,划选异常时间段
跳转到事务分析报表,通过响应排行发现该时间内A接口响应时间严重变慢
在A接口慢事务Top排行中选中一条具体事务,观察该事务的瀑布图,发现是数据库查询方法慢
跳转到该处日志,发现是意料外参数导致的全表查询
整个排查过程思路清晰且每个跳转均提供有力的数据支撑,没有多余噪音。该操作逻辑的背后则是多维数据的相互协作。
Focus使用以下三种数据结构来承载对事务、事件和状态的落地:
事务观测:基于GoogleDapper理念的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年的时候我们只做到了Version1.0 当中的组件(编码为1 的组件)。在后续几年的演化中逐步形成了现在的架构,系统也由一个ELK型的日志系统演化为了一个完善的APM系统。
Focus服务端本质上是一个流计算系统。而如何化解流计算系统固有的挑战是主要设计问题。我主要讲以下几个关键问题的决策:
数据状态问题。分布式服务的状态处理是设计中的重点,我们通过简单的数据分区处理方式,让同一个维度的数据进入同一个Partition。这样计算服务就可以无状态,无状态的好处是可以任意扩展,且避免了服务间的通信等一系列困难。但是分区模式会带来热点问题,这一块我们目前通过更细粒度的分区来缓解。
异构数据处理。Focus的数据处理要支持3种不同的数据结构,即需要3套Pipeline来处理。而Focus的服务端是同构设计的,这样的设计更利于运维的简化,因为同构服务的集群可以被抽象为一个服务去管理。为了做到这一点,每个实例都会同时运行截然不同的计算任务,会导致算力不均。为此我们设计了一个算力调度模块,可以动态或手动调整算力分配。
故障自愈。流计算也就是实时增量计算,它不像离线计算,Job出错了大不了再跑一次。增量运算对可靠性提出了更高的要求。在故障处理设计中,Focus依赖KafkaRebalance 机制来实现故障转移。当某个实例出现故障,Kafka会将原本由其负责运算的数据Rebalance到另一个正常的实例从而保证消费不会中断。
流计算框架选型。在上述约束下,最终我们发现Kafka Streams非常适合我们的场景,尤其是KafkaStreams作为一个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仍然在不断的迭代中,很多设计取舍也和公司规模和具体场景有很大关系,所谓抛砖引玉,欢迎大家和我们交流。