Observability:构建下一代托管接入服务
作者:来自 Elastic Vishal Raj, Marc Lopez Rubio
随着无服务器(serverless)的引入,向 Elastic Cloud 发送可观察性数据变得越来越容易。你可以在 Elastic Cloud Serverless 中创建一个可观察性无服务器项目,并将可观察性信号直接发送到 Elasticsearch 或通过我们一直称之为托管接收服务(类似于 Elastic APM 服务器的完全托管解决方案)发送。
你不再需要担心 APM 服务器的大小和配置。相反,你可以将数据直接发送到托管接收服务,让我们的平台为你完成艰难的扩展工作。
在这篇文章中,我们将分享我们如何迭代现有的 APM 服务器架构来创建托管接收服务。
APM 服务器入门
APM 服务器设计为单租户流程,可接收和丰富原始可观察性信号(跟踪、日志和指标)并将它们索引到 Elasticsearch。它还根据收到的信号生成多间隔(1 分钟、10 分钟和 60 分钟间隔)聚合指标(汇总),以有效地为可观察性 APM UI 提供支持。有关为什么需要聚合的更多信息,请在此处阅读。
虽然 Elastic Cloud 中当前的 APM Server 产品适用于具有可预测或已知负载的用例,但它有一些限制,我们旨在在下一代采集服务的第一次迭代中改进这些限制:
- APM Server 不会根据传入负载自动扩展,需要用户根据估计或实际的最大采集负载做出前期决策。
- 理想情况下,每个 APM Server 实例应为每个唯一的聚合维度集生成 1 个聚合指标文档。但是,在当前模型中,指标数量与 APM Server 的副本数量直接相关,这会影响聚合的有效性。
- 聚合是在内存中执行的,随着唯一组合数量的增加,需要的内存量也会增加。
- APM Server 中可观察性数据的采集和数据到 Elasticsearch 的索引是紧密耦合的。这两个过程之间的任何缓冲区都是基于内存的,因此,如果 Elasticsearch 正在扩展或配置不足,则回推最终可能会导致采集陷入停顿,并向客户端返回错误。
当我们尝试不同的实现方式时,请记住这些要点。
APM 服务器作为托管产品
当我们开始时,我们问自己,我们可以采取的最简单的方法是什么?这种方法效果如何?它能满足先前的要求吗?
最简单的方法是为每个项目配置一个 APM 服务器,并在每个项目的 APM 服务器上使用水平自动扩展。但是,这会导致大量计算资源浪费,因为可观察性项目不会通过 APM 服务器获取可观察性信号。
此外,它没有解决任何初始限制,也没有改善 Elastic Cloud 中的整体 APM 服务器体验。很明显,我们想要(并且需要)尝试多租户方法。再次,我们研究了可以采取的最简单的方法来尽可能缩短反馈循环。
多租户 APM 服务器
我们合乎逻辑的下一步是扩展当前的 APM 服务器代码库。多租户 APM 服务器的要求之一是能够区分不同的租户并路由到该租户的适当 Elasticsearch 实例。
我们想出了一个一致的哈希环负载均衡器,以便将同一租户路由到同一 APM 服务器。这将满足我们的有界聚合要求 (2),避免为同一事件的同一组唯一维度生成多个聚合文档。
然而,当我们继续设计多租户 APM 服务器的其余部分时,它看起来很像多个服务卷在一个盒子里(或分布式整体,这是我们想要避免的)。
此外,内存要求看起来相当大,以避免内存耗尽,并且性能相当好。
在初步可行性原型之后,很明显,在现有 APM 服务器上进行迭代无法满足我们的期望。我们重新回到绘图板,目标是从头开始设计一个多租户分布式系统,以实现预期的可靠性、可扩展性和可用性水平。
分解整体
回到绘图板!这次,我们决定根据 APM 服务器所属的有界上下文将其分解为较小的服务。使用这种分类,APM 服务器的 3 个主要职责非常明显:
- 提取信号并丰富它们。
- 为已知时间段生成聚合指标表示
- 在其时间段结束后对原始信号和聚合指标进行索引。
将 APM 服务器分解为较小服务的另一个优点是,它允许根据服务的特定需求独立扩展每个服务。这意味着更好的资源利用率、简化推理和服务维护。
提取服务
顾名思义,提取服务的目的是从各种代理(如 Elastic 或 OTel 客户端)提取信号。提取服务还执行简单的数据丰富,以充分利用遥测信号。
提取服务的可扩展性要求直接取决于发送数据的客户端数量和数据量。除了采集数据之外,该服务还对向服务发送数据的每个客户执行分布式速率限制。速率限制可防止突发数据淹没数据处理管道。
聚合服务
聚合或数据汇总是 Elastic 可观察性堆栈的重要组成部分。汇总指标允许 Kibana UI 更有效地显示服务的遥测信号,使你可以将时间范围从几分钟更改为几天或几年,而不会导致查询性能显著下降。本质上,它减少了 Elasticsearch 必须聚合的文档总数,与 SQL/Database-land 中的物化视图并无二致。
传统的 APM 服务器执行内存中聚合,但是,基于内存的方法对于具有自动扩展功能的多项目服务来说是不够的。此外,内存中聚合限制的表现并不理想,因为每种聚合类型都有单独的限制以避免内存不足问题。
由于我们想同时解决这两个问题(并且在对聚合流中的持久性实现进行了一些初步试验之后),我们决定使用键值(key-value)数据库 Pebble 的日志结构化合并 (log-structured merge - LSM) 树方法。
这项工作最终在 apm-aggregation 中实现,这是一个用于执行聚合的库,这些聚合主要受磁盘大小限制,内存要求要小得多。从 8.10 开始,APM Server 中也发布了基于 LSM 的聚合。我们有意保持库开放,以便为自管理和托管的 APM Server 共享相同的改进。
索引服务
索引过程缓冲数十、数百或数千个事件,并使用 _bulk API 将这些事件分批发送到 Elasticsearch。虽然本质上很简单,但该过程有一些复杂性,需要大量的工程工作才能正确完成:
- 必须将数据可靠地索引到 Elasticsearch 中。有两种主要场景需要重试以避免数据丢失:a.临时 _bulk 请求被拒绝(Elasticsearch 拒绝整个 _bulk 请求,因为它无法提供服务)。b. 临时单个文档被拒绝(_bulk 请求成功,但一个或多个文档未被索引)。
- 索引必须公平,但也与不同租户的数据量成比例。
第一个子点(1a)已通过 go-elasticsearch 客户端内置的请求重试在特定 HTTP 状态代码上正确解决。重试单个文档被拒绝需要更多的工程工作,并促使我们在共享索引组件(go-docappender)中为 APM 服务器和托管接收服务中的索引过程实现文档级重试。
第二点由基本分片架构和托管接收服务组件之间使用的传输解决。简而言之,每个项目都有一定数量的专用索引客户端,以确保基本的服务质量。
重新整合!
虽然分解服务使我们更接近目标,但我们仍然必须决定服务如何通信以及数据如何从一个服务流向另一个服务。
传统上,大多数微服务使用简单的基于 HTTP/RPC 的请求/响应方案进行通信。但是,这要求服务始终处于运行状态,或假设暂时不可用,应用程序会重试不健康的状态代码,或者使用类似服务网格的东西将请求路由到适当的应用程序实例,并可能依赖状态代码来允许服务网格透明地处理重试。
虽然我们考虑过这种方法,但一旦开始考虑不同的故障模式,它似乎就不必要地复杂和脆弱。我们研究了事件处理系统,毫不奇怪,我们开始考虑使用事件总线或队列作为通信手段。
对于我们的用例来说,使用事件总线而不是同步的基于 RPC 的通信系统有很多优势。主要优点是它将生产者和消费者分离(生产者生成数据,而消费者接收并处理数据)。这种解耦对于可靠性和弹性来说非常有利,并且允许在一段时间内进行不对称处理,直到自动扩展生效。
我们花了大量时间审查不同的事件总线技术,并毫不意外地决定,在许多领域最强大的竞争者是……Kafka!
使用 Kafka
由于久经考验的 Kafka 将成为服务之间的粘合剂,因此我们对能够提供高可用性和可靠性充满信心。事件总线提供的数据持久性使我们能够吸收消费(并产生流量峰值)延迟和持久层回推,同时让外部客户端对摄取路径感到满意。下一步是将数据管道整合在一起。
我们最初的尝试为每种信号类型生成了 Kafka 主题。每个主题都收到了多个项目的特定信号类型 —— 毫无疑问,这是给定堆栈中最具成本效益的方法。
初始测试和封闭测试版发布取得了良好的表现;然而,一旦项目数量(和数据量)增加,传呼机就开始响个不停。我们看到延迟的玻璃化时间以及糟糕的索引性能警报。在调查问题时,我们很快发现我们的多租户主题正在创建热点和嘈杂的邻居问题。此外,由于 Head-Of-Line 阻塞问题,索引服务难以持续满足我们的 SLO。
退一步来看,我们意识到 Elasticsearch 的单租户模型需要更高级别的数据隔离,以保证性能、防止嘈杂的邻居并消除 Head-Of-Line 阻塞问题。
我们将主题从多项目每个事件(每个信号类型)主题更改为每个项目多事件(多信号),即每个项目都会有自己的主题。每个项目的主题提供了更好的隔离,而 Elasticsearch 可以自动扩展而不会影响其他项目。
此外,考虑到 Kafka 分区的工作方式,当单个消费者无法应对负载时,它还允许分区扩展以满足不断增长的数据量。
观察系统
更简单的系统总是易于观察,虽然拆分我们的服务是出于必要,但也带来了可观察性挑战。更多的服务也可能意味着更多的(潜在)故障点。为了缓解这个问题,我们决定根据客户影响来观察我们的系统。
为此,我们决定使用服务级别目标 (Service Level Objectives - SLO) 来监控我们的服务。SLO 为我们提供了客观推断服务性能所需的框架,但我们并没有止步于此。由于我们的目标是衡量客户影响,我们绘制了关键的用户旅程并设计了我们的 SLO 来涵盖这些旅程。
下一个挑战是实施 SLO。对我们来说幸运的是,更广泛的可观察性团队正在努力推出服务级别目标 (SLO),我们成为首批用户之一。
为了支持支持 SLO 的服务级别指标 (Service Level Objectives- SLI),我们使用指标、日志和跟踪的组合仔细地检测了我们的服务(惊喜!)。由于我们的服务是高吞吐量服务,但吞吐量较低的 SLO 也由日志源提供支持,因此我们的大多数 SLO 都由指标提供支持。
由于我们专注于客户的影响以及可能出错的不同领域,因此我们从一开始就进行了非常广泛(和深入)的初始检测。它极大地方便了调查我们新采集平台中的页面和重复出现的问题。
如今,我们所有的用户旅程都使用 SLO 进行端到端监控,使我们能够主动检测和解决任何问题,以免对你(我们的客户)造成困扰。
准备好升级你的可观察性堆栈了吗?
托管接收服务旨在通过为用户提供无缝界面来升级 Elastic 的可观察性产品,以便用户无需考虑数据规模或花费工作时间计算基础设施要求以可靠地托管其当前和近期数据即可接收遥测信号。该服务已在 Elastic Cloud 上线,当你在我们的无服务器产品中创建可观察性项目时即可使用。
重新设计我们的可观察性平台的接收功能对我们来说非常有趣,我们希望它能帮助你升级可观察性堆栈。虽然这篇博文介绍了托管接收服务的高级架构,但还有更多内容要讨论。请留意未来的帖子,我们将在其中深入探讨各个组件。
准备好自己尝试一下了吗?开始免费试用。
想要获得 Elastic 认证吗?了解下一期 Elasticsearch 工程师培训何时开始!
原文:Ingesting data with Managed Intake Service — Search Labs