赞
踩
作者:来自 Elastic Jason Tedor
Elastic Cloud Serverless 架构
2022 年 10 月,我们引入了 Elasticsearch 的无状态架构。 我们该计划的主要目标是发展 Elasticsearch,以利用云原生服务提供的操作、性能和成本效率。
该计划成为我们最近宣布的名为 Search AI Lake 的更大努力的一部分,并作为我们新的 Elastic Cloud Serverless 产品的基础。 在这一努力中,我们的目标不仅是使 Elasticsearch 和 Kibana 等 Elastic Stack 产品更加云原生,而且还使其编排更加云原生。 我们设计并构建了一个由 Kubernetes 支持的新后端平台,用于编排 Elastic Cloud Serverless 项目,并改进了 Elastic Stack 产品,以便我们更容易在 Kubernetes 中编排。 在本文中,我们将详细介绍我们在此过程中做出的一些架构决策。 在以后的文章中,我们将更深入地探讨其中的一些方面。
我们选择 Kubernetes 来支持后端的主要原因之一是 Kubernetes 中拥有丰富的资源,可用于解决容器生命周期管理、扩展、弹性和资源管理问题。 我们制定了“以原生 Kubernetes 方式做事”的早期原则,即使这意味着 Elastic Stack 产品的重大演变。 我们构建了各种 Kubernetes 原生服务,用于管理、观察和编排 Elastic Stack 产品(例如 Elasticsearch 和 Kibana)的物理实例。 这包括用于配置、管理软件更新和自动扩展的自定义控制器/操作器; 以及用于身份验证、管理操作备份和计量的服务。
作为一点背景知识,我们的后端架构有两个高级组件。
控制平面是一个全局组件,数据平面由多个 “局部组件” 组成。 这些是各个云服务提供商 (Cloud Serice Provider - CSP) 区域中的各个 Kubernetes 集群。
我们的数据平面将部署在 AWS、Azure 和 Google Cloud 上。 在每个主要 CSP 中,我们将在几个 CSP 区域运营。 我们不是垂直扩展大规模 Kubernetes 集群,而是使用基于单元的架构来水平扩展独立的 Kubernetes 集群。 在每个 CSP 区域内,我们将运行许多 Kubernetes 集群。 这种设计选择使我们能够避免 Kubernetes 扩展限制,并且还可以在 Kubernetes 集群发生故障时充当较小的故障域。
我们进行的一场有趣的辩论是 “推与拉”。 特别是,全局控制平面应如何与数据平面中的各个 Kubernetes 集群进行通信? 例如,当创建一个新的 Elastic Cloud Serverless 项目并需要在数据平面的 Kubernetes 集群中进行调度时,全局控制平面是否应该将该项目的配置下推到选定的 Kubernetes 集群,或者应该在数据平面中的 Kubernetes 集群中进行调度。数据平面监视并从全局控制平面中提取该配置? 与往常一样,这两种方法都需要权衡。 我们选择推送模型是因为:
在每个主要 CSP 中,我们选择使用他们的托管 Kubernetes 产品(AWS EKS、Azure AKS 和 Google GKE)。 这是我们为减轻集群本身的管理负担而做出的早期决定。 虽然托管 Kubernetes 产品满足了这一目标,但它们在其他方面都是准系统。 我们希望对我们的工程师构建的 Kubernetes 集群更加有自己的看法,减轻我们工程团队的负担,并免费提供某些东西。 我们为工程团队提供的开箱即用的(非详尽的)类型有:
在内部,我们将这种包装的基础设施称为 “Managed Kubernetes Infrastructure- 托管 Kubernetes 基础设施”。 它是我们的基础构建块,使我们的工程团队能够专注于构建和运营他们创建的服务。
我们在这里采用的一个重要架构原则是,我们的 Kubernetes 集群被认为是一次性的。它们不是任何重要数据的真实来源,因此我们永远不会在 Kubernetes 灾难中遇到数据丢失,并且可以随时重新创建它们。这种级别的弹性对于保护我们客户的数据非常重要。这个架构原则将简化我们平台在我们规模上的可操作性。
俗话说,这是他们让你上钩的方式。我们之前概述了 Elasticsearch 的无状态架构,其中我们使用对象存储(AWS S3、Azure Blob 存储、Google Cloud 存储)作为主要数据存储。从高层次来看,使用主要云服务提供商(CSP)对象存储的两个主要成本维度是存储和 API 调用。存储维度相当明显且易于估算。但如果不加控制,对象存储 API 调用的成本可能会迅速膨胀。由于对象存储充当主要数据存储,并且每个分片的数据结构(如 translog)也存储在其中,这意味着每次写入 Elasticsearch 都会涉及对象存储,因此每次写入分片至少会产生一次对象存储 API 调用。对于持有多个分片并频繁接收写入的 Elasticsearch 节点,成本会迅速累加。为了解决这个问题,我们将 translog 写入演变为按节点执行,在节点上合并跨分片的 translog 写入,并每 200 毫秒将它们刷新到对象存储。
一个相关的方面是刷新(refreshes)。在 Elasticsearch 中,刷新会转换为对其支持数据存储的写入,而在无状态架构中,这意味着写入对象存储,从而产生对象存储 API 调用。由于某些用例需要高刷新率,例如每秒刷新一次,当 Elasticsearch 节点接收来自多个分片的写入时,这些对象存储 API 调用会迅速增加。这意味着我们必须在次优用户体验和高成本之间进行权衡。此外,这些刷新对象存储 API 调用与在那一秒内摄取的数据量无关,因此很难将其与用户感知的值联系起来。我们考虑了几种解决方法:
我们最终选择了将刷新与写入对象存储分离。与其让刷新触发写入对象存储(搜索节点会读取这些写入以访问最近执行的操作),不如让主分片直接将刷新数据(段)推送到搜索节点,并推迟到稍后时间再写入对象存储。由于我们仍然在对象存储中将操作持久化到 translog,因此这种推迟不会有数据丢失的风险。尽管这种推迟会增加恢复时间,但它使刷新触发的对象存储 API 调用数量减少了两个数量级。
我们在 Elastic Cloud Serverless 上的一个主要用户体验目标是消除用户管理其项目大小/容量的需求。虽然这种控制对于某些用户来说非常强大,但我们设想了一种更简单的体验,即 Elastic Cloud Serverless 能够自动响应增加的摄取率或更大量数据查询的需求。随着在无状态 Elasticsearch 架构中存储和计算的分离,这比以前更容易解决问题,因为我们现在可以独立管理索引和搜索资源。
我们遇到的一个早期问题是需要一个能够支持垂直和水平自动扩展的自动扩展器,这样当一个项目的需求增加时,我们既可以向上扩展到更大的节点,也可以向外扩展到更多的节点。此外,我们还遇到了 Kubernetes 水平 Pod 自动扩展器的可扩展性问题。为了解决这个问题,我们构建了自定义的自动扩展控制器。这些自定义控制器获取应用程序级别的指标(特定于要扩展的工作负载,例如索引与搜索),做出自动扩展决策,并将这些决策推送到 Kubernetes 中的资源定义。这些决策随后被实际执行,以将应用程序扩展到所需的资源级别。
有了这个框架,我们可以独立添加更量身定制的指标(例如搜索查询负载指标),从而为自动扩展决策增添智能。这将使 Elastic Cloud Serverless 项目随着时间的推移更加动态地响应用户的工作负载。
这些只是我们在构建 Elastic Cloud Serverless 过程中做出的一些有趣的架构决策。我们相信,这个平台为我们提供了一个基础,使我们能够随着时间的推移快速向用户交付更多功能,同时更易操作、高性能、可扩展且成本高效。敬请关注未来的几篇文章,我们将深入探讨上述概念中的一些内容。
准备在你的应用中构建 RAG 吗?想尝试使用矢量数据库的不同 LLMs 吗? 请查看我们在 Github 上的 LangChain、Cohere 等示例笔记本,并参加即将开始的 Elasticsearch 工程师培训!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。