在当前阶段,可观测性的建设并没有统一的执行路径。每家公司会根据自身的业务需求、运营模式和规模,形成一套独特的实践方案。为了应对业务规模的扩大和需求的变化,可观测团队必须持续优化和升级其架构,并始终保证可观测系统本身的高可用性。
本文详尽地描绘了滴滴从 2017 年至今,在四个不同阶段所遭遇的技术挑战,如单体应用阶段的资源瓶颈、运维成本的上升、分布式服务的通信问题等等。滴滴通过寻找并应用适宜的技术方案,逐渐战胜了这些技术难题,使其可观测架构始终能为业务提供强大的支持。
滴滴出行可观测架构负责人——钱威
TakinTalks 稳定性社区专家团成员,滴滴出行可观测架构负责人。深耕可观测领域多年,专注于架构设计与优化。带领团队完成了滴滴第二代到第四代的架构迭代。多个可观测开源项目的 Contributor。目前聚焦在滴滴可观测的稳定性建设和滴滴场景下的可观测性的实现与落地工作。
温馨提醒:本文约 7500 字,预计花费 12 分钟阅读。
「TakinTalks 稳定性社区」公众号后台回复 “交流” 进入读者交流群;回复“1026”获取课件资料;
大家先来看一个故事——
“20 世纪初,当时处于高速发展期的福特公司。有一天一台电机坏了,相关生产工作被迫停止。很多工人和专家都找不到问题在哪。直到请到了一个叫斯坦门茨的人,斯坦门茨检查后用粉笔在电机外壳画了一条线,说打开电机,把记号处的线圈减少 16 圈。修理工照做后,故障排除,生产随即恢复。”
我们在工作或在开发过程中,时常会遇到这样的场景——让你一头雾水,不知道从何下手的难题,但是总有那么一两个“专家”一眼就能洞察问题所在。那么,我们需要思考一下,这到底是好事还是坏事?
滴滴作为一家出行平台,业务涵盖快车、专车、顺风车、共享单车等多个领域。每天有千万的用户和司机在平台上进行交互和使用,服务之间形成了复杂的依赖关系。在如此大规模的分布式系统中,故障排查和性能优化无疑是一项复杂的任务。
每次都依赖于个别专家的经验显然是无法控制的,也无法保证结果。因此,我们更愿意通过不断地演进可观测的架构,来支持业务的快速迭代和创新。
滴滴可观测系统通用架构主要包含几个部分,如下图所示。
我们会采集目标主机或其他的相关指标,经过传输链路后,某些指标可能会经过计算模块进行处理,然后再写回系统中。随后,这些数据会被存储起来。基于这些存储的数据,查询功能可以为上层应用提供数据展示,如仪表板、数据大盘、报警和事件等。
需要注意的是,每个模块需要完成的任务或实现的功能各不相同。例如,查询模块可能需要负责数据路由、聚合以及实现 DSL 等功能,这些功能通常在查询层进行实现。
数据存储的实现方式有很多种,如 InfluxDB、RRDtool、Prometheus、Druid、ClickHouse 等,都可以作为可观测系统的存储方案。
传输模块在系统中起到连接的作用,常见的消息队列就是用在这一模块中。当我们提到消息队列时,大家首先想到的可能是 Kafka,当然也有一些较为小众的选择,如 NSQ。
计算模块的任务则是将大量的指标转换成我们所需的形式,可能会去除一些维度进行计算。Flink、Spark 等工具在这一模块中都是常见的选择。
对于数据采集,也有许多丰富的工具可以选择,如 Telegraf、Node exporter,以及最近推出的 Grafana Agent 等。
当业务需求发生变化时,存储模块的性能问题通常是最先暴露出来的。在 2017 年以前,滴滴主要使用 InfluxDB 作为存储选择。我们根据业务服务的维度将 InfluxDB 实例进行了拆分,这样的设计便带来了一些问题。
首先,单机版本的性能存在瓶颈。例如,我们可能会遇到查询量较大的情况,如查询跨度长或查询数据多,这种情况下很可能会出现内存溢出(OOM)的问题。这也是社区中经常讨论的问题。
再者,我们采用的分片方式也存在问题。我们是按照服务进行拆分的,例如,如果今天有 50 个服务,那可能需要 50 个或更少的实例。但如果服务数量在明天增加到 500 个,那么运维成本将随之显著增加。特别是在当前大家普遍采用微服务架构的情况下,这种运维成本将会非常高。
为了解决上述问题,我们在 2017 年引入了 RRDTool。在此期间,RRDTool 取代了 InfluxDB,成为滴滴可观测的主要存储工具。
在 RRDTool 的设计中,我们采用了一致性哈希算法,在读写链路中进行多个 RRDTool 实例的分片。这种哈希算法的过程是先将所有的 Tag 打平,然后排序,最后再进行哈希,分配到各个实例中。
除此之外,我们还引入了一个名为“索引”的服务。这个服务的主要任务是满足产品需求。比如,我们可能需要提供服务列表,当用户选择了他们自己的服务后,需要知道该服务下有哪些指标,以及每个指标下有哪些 Tag。这种需求需要一个高效的索引服务来完成。
基于 RRDTool 的架构改进带来了两大成果。首先,它解决了 InfluxDB 的热点问题。我们原来是按照服务去拆分实例,现在我们将这些曲线分散到各个实例上。其次,这也减轻了 InfluxDB 的运维成本,因为我们采用了相对自动化的分片方式。
在 2018 年以后,我们面临了新的挑战。由于 RRDTool 的设计原理是每条曲线一个文件,因此,当数据规模扩大时,对 IO 的需求也随之增大。我们的 IOPS 已经超过了 3 万,这就需要我们增加更多的设备,例如具有高 IO 性能的机器,以解决这个问题。但是,这导致成本逐渐增高,且问题愈发严重。同时,可观测性中的读写是正交的,读写优化存在冲突——写通常是所有曲线写入最新的部分,而读通常是读取多条曲线或某条曲线长时间的数据。
(纵向为 Writes,横向为 Reads)
那么,我们如何解决这个问题呢?经过分析,我们发现 80%的查询都集中在最近两个小时内,因此,我们设计了一个冷热分层策略。这个策略的核心就是将压缩后的数据存储在内存中。压缩主要针对两个方面,一是时间戳,二是值。由于时间戳产生的时间间隔通常比较固定,而值的变化往往较为平缓,这为我们的压缩策略提供了依据。
基于这个原理,我们内部创建了一个名为"Cacheserver"的服务,主要服务于最近两小时的数据,采用了全内存的设计。这种设计使得用户查询的延迟从 10 秒降低到了 1 秒以内,每个数据点的存储由原来的 16 字节降低到了 1.64 字节。
整个设计可以通过上述图示来理解。首先是冷热分层,RRDTool 和 Cacheserver 共同完成了整个存储任务。以图示右半部分为例,原始的时间戳为 350、360、370、381,存储这些数据需要 256 比特。但经过压缩后,只需要 88 比特就足够了。这只是四个时间戳的情况,如果时间戳更多,那么压缩效果会更加显著。
随着用户接入的组件不断增多,用户的查询需求也变得越来越复杂。在我们的使用场景中,一旦 RRDTool 进行了降采,我们就无法再查看到原始数据。
面对这种情况,我们开始思考如何设计一个能满足用户当前和未来需求的系统。我们改变了问题解决的策略,不再针对每个具体情况单独设计方案。例如,如果过去有新增的查询形态,我们会需要编码并上线一个新的函数。而现在,我们选择直接利用业界的生态。
当时,Prometheus 是非常流行的。我们将目标从引入生态转变为引入 Prometheus 的生态。选择 Prometheus 的原因是,随着 K8s 的普及,Prometheus 已经成为了监控系统的事实标准。许多业界大厂和流行的厂商都在为 Prometheus 持续贡献代码和架构。
然而,如果我们选择引入 Prometheus 的生态,就无法继续使用 RRDTool,因为它无法兼容 Prometheus 的生态。这就需要我们寻找新的存储方案。
难点 1:新的存储方案如何选择?
在面临新的存储方案选择时,我们主要考虑了 Cortex、Thanos 和 VictoriaMetrics(简称 VM)。这些方案都是为了弥补 Prometheus 本身的一些缺陷而设计的,因为 Prometheus 从诞生之初就定位为单机存储,不支持长期存储,也没有高可用性。因此,Cortex 和 Thanos 在当时成为了业界主要的解决方案。
(调研业界 Prometheus 相关方案)
在对比这些方案时,我们发现 Cortex 和 Thanos 都能有效解决 Prometheus 的原生缺点。从成本角度考虑,由于 Thanos 和 Cortex 都采用了对象存储,因此它们的成本相对较低。但是,这两个方案由于使用了大量的第三方服务,如果公司没有对象存储或者没有云服务,那么这些组件的维护工作可能就需要由可观测团队来完成。
(RRDTool 与 VictoriaMetrics 方案对比)
相比之下,VM 与 RRDTool 相比,它是完全兼容 Prometheus 的。此外,我们之前提到过降采策略,RRDTool 的数据在超过两小时后会进行降采,一旦降采,我们就无法查看到原始数据。而 VM 本身不进行降采,这为我们带来了更多可能性。在降低存储成本方面,VM 的表现较好,在我们的环境测试中,其存储成本只有 RRDTool 的 1/20 左右。在数据上报形态上,Prometheus 是 Pull 形式,而 RRDTool 只能支持 Push 形式,并且只支持私有协议。但 VM 既支持 Pull 也支持 Push,对流行的数据上报协议也有良好的支持。
难点 2:如何引入 Prometheus 生态?
那么,我们是否可以简单地将存储方案替换为 VM 呢?实际上,答案是否定的。在引入新的生态系统时,我们首先需要考虑现有的公司方案。引入新的生态并不意味着要完全颠覆现有的产品架构,不能简单地进行替换。
为了引入新的生态,滴滴进行了一些改造。如图所示,绿色部分是使用 Prometheus 原生方案所需完成的工作。只要被监控的对象支持"/metrics"这样的接口,Prometheus 便可以进行数据拉取。对滴滴而言,我们原来的架构是基于采集、传输、存储的 Push 模型。因此,我们在采集部分增加了一个兼容 Prometheus 的 Adapter。在原有基础上,对于那些新增并且支持 Prometheus 拉取的服务,我们也可以使用自有的采集方法进行数据拉取。
在生态引入的成果方面,我们已经支持了 Prometheus 的数据采集,并且可以支持 PromQL 的图表查看和报警这两个常见场景。此外,我们还在图表查看这个维度上增加了一些新的功能,比如增加了 TopK/BottomK 等图表维度的 Outlier 能力。这样,如果一个服务有很多个实例,我们就可以利用 TopK/BottomK 这样的功能找出异常点。
在回馈社区方面,我们向 VM 官方和 Prometheus 社区递交了一些 PR,以此为整个社区做出贡献。
众所周知,可观测系统的目的是保障业务的稳定性。那么,我们如何保障可观测系统本身的稳定性呢?首先,我们需要探讨如何监测这个可观测的系统。是否可以在自身的系统上配置一些策略?或者建立一些仪表盘?或者采取其他一些方式?在这方面,我将分享一些我们的实验和思考。
我们不能让可观测的系统对其本身做观测。例如,如果存储系统出现故障,而查询数据的方式是从自身的存储中查询,那么就会形成循环依赖。因此,第一个原则就是不能让可观测的系统自观测。第二个原则与第一个原则有关,即需要一套独立的数据采集和报警服务来进行观测。
在我们的实践中,主要采用了两种方法。
第一种方法用于监测流量,适用于数据采集、传输和存储。这种方法主要通过使用 Exporter、Prometheus 和 Alertmanager 来进行自我监测。例如,如果存储写入流量突然变化,就可以使用这套系统进行自我监测。
另一种方法是监测能力。以报警为例,最简单的方法是设置一条始终会触发阈值的报警,但可能不会发送实时消息或短信通知。一旦报警事件中断,可能是因为报警系统本身存在问题,或者报警系统所依赖的存储查询存在问题。在这种情况下,我们可以通过设置探测器和进行端到端的检查来解决问题。
我们可以从两个方面来考虑:一是通过架构优化, 二是采取常用的保障手段。
要点 1:鸡蛋不要放在一个篮子里
对于架构优化,一个简单的原则就是不要把所有的鸡蛋放在一个篮子里。我们可以通过以下的设计实现这一点。
(VictoriaMetrics 存储多集群设计)
滴滴主要从事打车业务,我们的网约车和非网约车业务的观测数据各自存储在不同的存储集群上,这就是我们采用的 VM 多集群设计。例如,如果非网约车业务实例出现问题,我们希望这不会影响到网约车业务,反之亦然。因此,我们在存储方面进行了多集群的设计。
(传输多集群设计)
在数据传输方面,我们的设计理念也是类似的,但有一点区别在于,传输和存储会用到不同的分片策略,这是因为它们的负载特性不同。例如,某个业务的传输量非常大,但存储查询的量却非常小,这种情况下,我们会在传输端对数据进行拆分,在存储端只需要保证数据的写入即可。它们可以共享同一存储集群。
要点 2:及时扔掉坏鸡蛋
另外还有一个原则,我们称之为“及时扔掉坏鸡蛋”。在传输模块中,除了写入存储,还有其他的下游模块,如流式报警等。
因此,如果某个子系统因为某些原因运行变慢,从而影响了整个传输模块,这是我们不愿看到的。我们希望在子系统运行变慢或出现问题时,能够及时将其剔除出系统,即熔断策略。在某些情况下,我们可以自动进行熔断,并尝试不断恢复这个子系统。如果它成功恢复,那我们就会重新将这个系统接入。
熔断、降级、多维度限流:
除了熔断和降级,我们还有其他保障手段,如多维度的限流。多维度限流采取灵活策略对请求进行限制,例如,一些持续且高频的跨度长时间的查询,比如几个月甚至几年的数据查询,我们就会应用多维度的限流手段。
慢查治理:
另一个保障手段是慢查的治理,这涉及到对大量曲线的查询。比如,一次查询涉及到了上百万的曲线,此时我们需要进行慢查发现,然后进行治理。在一些重点保障的时期,我们会开启这些策略,一旦识别到异常,就采用多维度限流,根据它的特征进行限流或者直接禁用。
多活:
内部可观测的多活,我们采用的方式是做单元化。例如,如果 A 机房和 B 机房的专线中断,我们需要保障用户可以单独访问相应机房的数据。
容量评估体系:
我们还有容量评估体系。因为在可观测架构和业务流量或订单量的增长可能不成正比,所以需要一套自身的容量评估体系。每家公司的业务模型可能不同,所以这个体系需要建立起来,对于保障手段来说,这是有帮助的。
预案、演练:
我们还会制定预案并进行演练,以保证这些手段是有效的。
可观测性这个主题在 2021 或 2022 年是一个非常热门的话题。有人可能会觉得,如果不谈论可观测性,就相当落后了。我们先来看一下各大厂对可观测性的定义。
可观测性是可帮助团队有效调试其系统的工具或技术解决方案。可观测性基于对事先未定义的属性和模式的探索。——来源 Google
可观测性是指能够通过检查系统或应用的输出、日志和性能指标来监控、测量和理解系统或应用的状态。——来源 RedHat
可观测性是指您仅根据所了解的外部输出对复杂系统内部状态或条件的理解程度。——来源 IBM
我在这里分别引用了 Google、RedHat 和 IBM 对可观测性的定义,他们有两个共识。第一个是,可观测性是能从外部理解系统内部的状态,而这些状态并不需要是已知的。第二个共识是,可观测性有许多手段,包括日志、指标、事件等。
那么,如何实现可观测性呢?各大厂都有自己的实现方式。Google 推荐使用其云平台 GCP,RedHat 推荐使用 OpenShift Observability,IBM 有其自己的产品 Instana Observability,而 Grafana 推荐使用 LGTM(Loki、Tempo、Mimir)。
综合来看,实现可观测性的方法大概有三种。第一种是购买 SaaS 厂商的服务,第二种是尽可能地采集和存储详尽的可观测数据,第三种是关联多种观测数据。
对于滴滴,第一种实现方式并不适合,因此我们优先排除。
至于第二种实现方式是“尽可能详尽”,于是我们将观测数据分为两个维度,即 Dimensionality 和 Cardinality。Dimensionality 类似于标签的概念,例如时间戳、版本、顾客 ID 等。Cardinality 则以顾客 ID 为例,可能有从 1 万 01 到 1 万 9999 的数据。这种方案优点是能采集大量数据,但缺点是实现成本高、资源消耗大,且数据利用率偏低。
第三种实现方式是关联多种观测数据,常见的观测数据包括 Metric、Trace、Log。Metric 数据属于高层次抽象,能告诉你错误数,但无法提供具体错误信息。Trace 数据主要用于跨服务关联,比如一个请求经历了哪些服务。Log 数据则是开发人员偏好的信息,它提供最详细的、人类可读的数据。然而,这种关联多种观测数据的方式,其缺点是架构实现相对复杂。
在滴滴,我们借鉴了上述两种方法,将数据分为低基数和高基数两类。低基数指的是指标数据,而高基数则是日志数据。我们将这两种数据分别存储在不同的数据库中,并建立它们的关联关系。
举个例子,如果在一段时间内我们收集到两个错误日志,我们就会将这个错误数“2”上报到时序数据库。同时,我们将对其中一条错误日志进行采样,并将其存储在 Exemplar DB 中。然后,我们会通过标签将时序数据库和 Exemplar DB 进行关联。
滴滴的可观测性实践成果非常显著。在建立可观测性之前,我们在排查故障时需要登录到机器上并检索日志。如果有幸找到了问题所在的机器,那就算是幸运的。但如果并非问题出在这台机器,甚至不是这个服务,我们就需要重复上述的操作。而且,即使经过这样的操作,是否能找到问题也是不确定的。
然而,在建立了可观测性之后,当我们收到报警消息时,我们可以直接查看与这条报警相关联的日志原文。查阅了日志原文之后,如果认为没有大问题,可以暂时不进行处理。如果是紧急情况,我们就会启动紧急处理流程。
此外,当我们在查看图表时,如果发现某个指标突然升高,想要知道是什么原因导致的,我们可以使用下钻功能。这个功能不仅可以让我们查看日志原文,如果日志中包含 Trace 信息,还可以将这个 Trace 信息提取出来。然后可以将 Trace 信息下钻到专门的 Trace 产品进行进一步的处理。
四、总结展望 滴滴的可观测性架构的发展实际上是基于不同的需求、场景和时代背景,选择了最适宜的解决方案。
我们对接了业界一些成熟的生态系统,并将这些生态系统融入到我们的系统中,这极大地帮助我们完成了许多工作,也提升了我们的工作效率。同时,在建设可观测性平台的过程中,我们也采用了一些策略来实现观测系统自身的稳定性保障。
值得注意的是,可观测性的建设并没有一种统一的实现方式,每家公司都有其自身的特色。因此,各公司需要根据自己的特点去定制专门的解决方案,并根据实际情况不断选择和调整最合适的方案。(全文完)
1、滴滴是否有专门的技术团队去维护可观测架构?Prometheus 的横向扩展能力相对有限。InfluxDB 具体有哪些问题?
2、如何去度量一个架构的可观测性?有什么建议吗?
3、Metric 的时效性有必要做到秒级吗?
4、接口偶发性超时,调用链只能看到超时接口名称,看不到内部方法,无法定位根因,也难以复现,怎么办?
以上问题答案,欢迎点击“阅读全文”,观看完整版解答!
声明:本文由公众号「TakinTalks 稳定性社区」联合社区专家共同原创撰写,如需转载,请后台回复“转载”获得授权。
本文由博客一文多发平台 OpenWrite 发布!