Elasticsearch 集群 200 TB+

Elasticsearch 集群 200 TB+

许多人都在使用 Elasticsearch。 但是,当您想使用它“以特别大的容量”存储日志时会发生什么? 经历多个数据中心中任何一个的故障是否也不会造成痛苦? 你应该建造什么样的架构,你会遇到什么陷阱?

我们 Odnoklassniki 决定使用 Elasticsearch 来解决日志管理问题,现在我们与 Habr 分享我们的经验:包括架构和陷阱。

我是 Pyotr Zaitsev,在 Odnoklassniki 担任系统管理员。 在此之前,我也是一名管理员,曾使用 Manticore Search、Sphinx search、Elasticsearch。 也许,如果出现另一个...搜索,我也可能会使用它。 我还自愿参与了一些开源项目。

当我来到 Odnoklassniki 时,我在面试时鲁莽地说我可以使用 Elasticsearch。 在我掌握了它的窍门并完成了一些简单的任务后,我被赋予了改革当时存在的日志管理系统的重大任务。

需求

系统要求制定如下:

  • Graylog 将被用作前端。 因为公司已经有使用该产品的经验,程序员和测试人员都知道它,对他们来说熟悉且方便。
  • 数据量:平均每秒50-80万条消息,但如果出现故障,那么流量不受任何限制,可以是每秒2-3百万行
  • 在与客户讨论了处理搜索查询的速度要求后,我们意识到使用此类系统的典型模式是这样的:人们正在寻找最近两天的应用程序日志,并且不想等待超过第二个用于公式化查询的结果。
  • 管理员坚持认为,如果需要,系统可以轻松扩展,而不需要他们深入研究其工作原理。
  • 因此,这些系统需要定期进行的唯一维护任务就是更换一些硬件。
  • 此外,Odnoklassniki 拥有优秀的技术传统:我们推出的任何服务都必须在数据中心故障(突然的、无计划的、绝对随时发生的)中幸存下来。

实施这个项目的最后一个要求是我们花费最多的,我将更详细地讨论这一点。

星期三

我们在四个数据中心工作,而 Elasticsearch 数据节点只能位于三个(出于许多非技术原因)。

这四个数据中心包含大约 18 个不同的日志源——硬件、容器、虚拟机。

重要特点:集群在容器中启动 波德曼 不是在物理机器上,而是在 自有云产品一云。 容器保证有 2 个核心,类似于 2.0Ghz v4,如果剩余核心闲置,则可以回收它们。

换句话说:

Elasticsearch 集群 200 TB+

拓扑结构

我最初看到解决方案的一般形式如下:

  • Graylog域的A记录后面有3-4个VIP,这是日志发送到的地址。
  • 每个 VIP 都是一个 LVS 平衡器。
  • 之后,日志进入 Graylog 电池,一些数据是 GELF 格式,一些是 syslog 格式。
  • 然后,所有这些都会大批量写入一组 Elasticsearch 协调器中。
  • 然后它们依次向相关数据节点发送写入和读取请求。

Elasticsearch 集群 200 TB+

术语

也许不是每个人都详细了解这些术语,所以我想详细说明一下。

Elasticsearch 有多种类型的节点 - 主节点、协调节点、数据节点。 还有另外两种类型用于不同的日志转换和不同集群之间的通信,但我们仅使用列出的那些。

总音量
它对集群中存在的所有节点执行 ping 操作,维护最新的集群映射并将其在节点之间分发,处理事件逻辑,并执行各种集群范围的内务处理。

协调员
执行一项任务:接受来自客户端的读取或写入请求并路由此流量。 如果有写入请求,它很可能会询问 master 应该将其放入相关索引的哪个分片中,并进一步重定向请求。

数据节点
存储数据,执行来自外部的搜索查询并对位于其上的分片执行操作。

灰色日志
这有点像 ELK 堆栈中 Kibana 与 Logstash 的融合。 Graylog 结合了 UI 和日志处理管道。 在底层,Graylog 运行 Kafka 和 Zookeeper,它们作为集群提供与 Graylog 的连接。 Graylog可以在Elasticsearch不可用的情况下缓存日志(Kafka),并重复不成功的读写请求,按照指定的规则对日志进行分组和标记。 与 Logstash 一样,Graylog 具有在将行写入 Elasticsearch 之前对其进行修改的功能。

此外,Graylog具有内置的服务发现功能,可以基于一个可用的Elasticsearch节点获取整个集群图并通过特定标签进行过滤,从而可以将请求定向到特定容器。

从视觉上看,它看起来像这样:

Elasticsearch 集群 200 TB+

这是特定实例的屏幕截图。 在这里,我们根据搜索查询构建直方图并显示相关行。

指数

回到系统架构,我想更详细地讨论我们如何构建索引模型以使其一切正常工作。

在上图中,这是最低级别:Elasticsearch 数据节点。

索引是由 Elasticsearch 分片组成的大型虚拟实体。 就其本身而言,每个分片只不过是一个 Lucene 索引。 而每个 Lucene 索引又由一个或多个段组成。

Elasticsearch 集群 200 TB+

在设计时,我们认为为了满足大量数据读取速度的要求,需要将这些数据均匀地“分布”在数据节点上。

这导致每个索引的分片数量(带有副本)应该严格等于数据节点的数量。 首先,为了确保复制因子等于XNUMX(也就是说,我们可以丢失一半的集群)。 其次,为了在至少一半的集群上处理读写请求。

我们首先确定储存时间为30天。

分片的分布可以用图形表示如下:

Elasticsearch 集群 200 TB+

整个深灰色矩形是一个索引。 其中左侧的红色方块是主分片,即索引中的第一个分片。 蓝色方块是复制品碎片。 它们位于不同的数据中心。

当我们添加另一个分片时,它会转到第三个数据中心。 最后,我们得到了这样的结构,它可以在不丢失数据一致性的情况下丢失 DC:

Elasticsearch 集群 200 TB+

索引的旋转,即创建一个新索引并删除最旧的索引,我们将其设置为 48 小时(根据索引使用模式:最近 48 小时搜索最频繁)。

这种索引轮转间隔是由于以下原因:

当搜索请求到达特定数据节点时,从性能角度来看,如果查询一个分片的大小与节点的臀部大小相当,则查询一个分片会更有利可图。 这允许您将索引的“热”部分保留在堆中并快速访问它。 当“热点部分”较多时,索引搜索的速度就会降低。

当节点开始在一个分片上执行搜索查询时,它会分配与物理机的超线程核心数量相等的线程数量。 如果搜索查询影响大量分片,则线程数量会成比例增长。 这会对搜索速度产生负面影响,并对新数据的索引产生负面影响。

为了提供必要的搜索延迟,我们决定使用 SSD。 为了快速处理请求,托管这些容器的机器必须至少有 56 个核心。 选择数字 56 作为有条件的足够值,它确定 Elasticsearch 在操作期间将生成的线程数。 在Elasitcsearch中,许多线程池参数直接取决于可用核心的数量,而根据“更少的核心-更多的节点”的原则,这反过来又直接影响集群中所需的节点数量。

结果,我们发现平均一个分片的大小约为 20 GB,每个索引有 1 个分片。 因此,如果我们每 360 小时轮换一次,那么我们就有 48 个。 每个索引包含 15 天的数据。

数据写入和读取电路

让我们弄清楚数据是如何记录在这个系统中的。

假设某个请求从 Graylog 到达协调器。 例如,我们想要索引 2-3 千行。

协调员收到 Graylog 的请求后,向 master 询问:“在索引请求中,我们专门指定了一个索引,但没有指定将其写入哪个分片。”

主节点响应:“将此信息写入71号分片”,然后直接发送到71号主分片所在的相关数据节点。

之后,事务日志被复制到位于另一个数据中心的副本分片。

Elasticsearch 集群 200 TB+

搜索请求从 Graylog 到达协调器。 协调器根据索引进行重定向,而 Elasticsearch 使用循环原理在主分片和副本分片之间分配请求。

Elasticsearch 集群 200 TB+

180 个节点的响应不均匀,在它们响应的同时,协调器正在积累已经被更快的数据节点“吐出”的信息。 此后,当所有信息都已到达或请求超时时,它会将所有信息直接提供给客户端。

整个系统平均在 48-300 毫秒内处理过去 400 小时的搜索查询,不包括那些带有前导通配符的查询。

Elasticsearch 之花:Java 设置

Elasticsearch 集群 200 TB+

为了让一切按照我们最初想要的方式工作,我们花了很长时间调试集群中的各种东西。

发现的第一部分问题与 Elasticsearch 中默认预配置 Java 的方式有关。

第一个问题
我们已经看到大量报告表明,在 Lucene 级别,当后台作业运行时,Lucene 段合并会失败并出现错误。 同时,日志中清楚地表明这是一个 OutOfMemoryError 错误。 我们从遥测中看到髋部是自由的,但尚不清楚手术失败的原因。

事实证明,Lucene 索引合并发生在臀部之外。 而且容器在消耗的资源方面受到相当严格的限制。 只有堆可以容纳这些资源(heap.size 值大约等于 RAM),并且如果由于某种原因它们无法容纳在限制之前剩余的约 500MB 中,则某些堆外操作会因内存分配错误而崩溃。

修复非常简单:增加了容器可用的 RAM 量,之后我们忘记了我们甚至遇到过这样的问题。

问题二
集群启动后4-5天,我们注意到数据节点开始周期性地脱离集群并在10-20秒后进入集群。

当我们开始弄清楚这一点时,发现 Elasticsearch 中的堆外内存不受任何方式控制。 当我们为容器提供更多内存时,我们能够用各种信息填充直接缓冲池,并且只有在从 Elasticsearch 启动显式 GC 后才会清除它。

在某些情况下,此操作需要相当长的时间,在此期间集群设法将该节点标记为已退出。 这个问题描述得很好 这里.

解决方案如下:我们限制了 Java 使用堆外大量内存来执行这些操作的能力。 我们将其限制为 16 GB (-XX:MaxDirectMemorySize=16g),确保更频繁地调用显式 GC 并更快地处理,从而不再破坏集群的稳定性。

问题三
如果您认为“节点在最意想不到的时刻离开集群”的问题已经结束,那您就错了。

当我们配置索引工作时,我们选择了 mmapfs 来 减少搜索时间 在具有良好分割的新鲜碎片上。 这是一个很大的错误,因为当使用 mmapfs 时,文件被映射到 RAM,然后我们使用映射的文件。 正因为如此,事实证明,当 GC 尝试停止应用程序中的线程时,我们会在很长一段时间内到达安全点,并且在到达安全点的途中,应用程序停止响应主服务器关于其是否存活的请求。 因此,master 认为该节点不再存在于集群中。 此后,5-10秒后,垃圾收集器开始工作,节点恢复活力,再次进入集群并开始初始化分片。 这一切感觉很像“我们应得的作品”,不适合任何严肃的事情。

为了摆脱这种行为,我们首先切换到标准 niofs,然后,当我们从 Elastic 的第五个版本迁移到第六个版本时,我们尝试了 Hybridfs,这个问题没有重现。 您可以阅读有关存储类型的更多信息 这里.

问题四
然后还有另一个非常有趣的问题,我们处理了创纪录的时间。 我们抓了它2-3个月,因为它的模式完全无法理解。

有时,我们的协调员通常会在午餐后的某个时间进行 Full GC,然后就再也没有从那里返回。 同时,在记录 GC 延迟时,它看起来像这样:一切都很顺利,很好,很好,然后突然一切都变得非常糟糕。

起初,我们认为有一个邪恶的用户正在发起某种请求,使协调器脱离工作模式。 我们记录了很长时间的请求,试图弄清楚发生了什么。

结果是,当用户发起巨大请求并到达特定 Elasticsearch 协调器时,某些节点的响应时间会比其他节点长。

当协调器等待所有节点的响应时,他会累积从已响应的节点发送的结果。 对于 GC,这意味着我们的堆使用模式变化得非常快。 而我们使用的GC无法应对这个任务。

在这种情况下,我们发现改变集群行为的唯一解决方法是迁移到 JDK13 并使用 Shenandoah 垃圾收集器。 这解决了问题,我们的协调员不再跌倒了。

这就是 Java 问题结束和带宽问题开始的地方。

Elasticsearch 的“浆果”:吞吐量

Elasticsearch 集群 200 TB+

吞吐量问题意味着我们的集群工作稳定,但在索引文档数量达到峰值时以及在操作过程中,性能不足。

第一个症状是:在生产中的一些“爆发”过程中,突然产生大量日志时,Graylog 中开始频繁闪烁索引错误 es_rejected_execution。

这是因为,在 Elasticsearch 能够处理索引请求并将信息上传到磁盘上的分片之前,一个数据节点上的 thread_pool.write.queue 默认情况下只能缓存 200 个请求。 并且在 Elasticsearch 文档 关于这个参数的说法很少。 仅指示最大线程数和默认大小。

当然,我们去扭曲这个值并发现了以下内容:具体来说,在我们的设置中,最多 300 个请求被很好地缓存,并且更高的值会导致我们再次飞入 Full GC。

此外,由于这些消息是在一个请求内到达的一批消息,因此有必要调整 Graylog,使其不经常小批量写入,而是大批量写入,或者如果批量尚未完成,则每 3 秒写入一次。 在这种情况下,事实证明,我们在 Elasticsearch 中写入的信息不是在两秒内可用,而是在五秒内可用(这非常适合我们),但是为了推动大量数据而必须进行的重发次数信息堆栈减少。

当某些东西在某个地方崩溃并疯狂地报告它时,这一点尤其重要,以免得到完全垃圾邮件的 Elastic,并且在一段时间后 - Graylog 节点由于缓冲区堵塞而无法运行。

此外,当我们在生产中遇到同样的爆炸性增长时,我们收到了程序员和测试人员的抱怨:当他们真正需要这些日志时,却提供得非常缓慢。

他们开始想办法。 一方面,很明显,搜索查询和索引查询本质上都是在同一台物理机器上处理的,并且无论如何都会有一定的缩减。

但这可以部分规避,因为在 Elasticsearch 的第六个版本中出现了一种算法,允许您在相关数据节点之间分配查询,而不是根据随机循环原理(执行索引并保存主数据的容器)分片可能会非常繁忙,将无法快速响应),但是将此请求转发到具有副本分片的负载较少的容器,这会响应得更快。 换句话说,我们得到了 use_adaptive_replica_selection: true。

阅读图片开始是这样的:

Elasticsearch 集群 200 TB+

当我们要写入大量日志时,过渡到该算法可以显着缩短查询时间。

最后,主要问题是轻松拆除数据中心。

在与一台 DC 失去连接后,我们立即希望从集群中得到什么:

  • 如果我们在发生故障的数据中心中有一个当前的主节点,那么它将被重新选择并作为角色移动到另一个 DC 中的另一个节点。
  • master会快速从集群中删除所有不可访问的节点。
  • 根据剩下的,他会明白:在丢失的数据中心我们有这样那样的主分片,他会在剩下的数据中心快速提升互补的副本分片,我们会继续对数据建立索引。
  • 因此,集群的写入和读取吞吐量将逐渐下降,但总的来说一切都会正常进行,尽管缓慢但稳定。

事实证明,我们想要这样的东西:

Elasticsearch 集群 200 TB+

我们得到以下结果:

Elasticsearch 集群 200 TB+

这是怎么发生的?

当数据中心垮掉的时候,我们的master就成了瓶颈。

为什么呢?

事实上,master有一个TaskBatcher,它负责在集群中分发某些任务和事件。 任何节点退出、从副本到主分片的任何提升、在某处创建分片的任何任务 - 所有这些都首先进入 TaskBatcher,在那里它在一个线程中按顺序进行处理。

当一个数据中心撤出时,幸存数据中心的所有数据节点都认为有责任通知Master“我们丢失了某某分片、某某数据节点”。

与此同时,幸存的数据节点将所有这些信息发送给当前的主节点,并尝试等待他接受的确认。 他们没有等到这一刻,因为主人接到任务的速度比他回答的速度还要快。 节点重复请求超时,此时的主节点甚至没有尝试回答它们,而是完全专注于按优先级对请求进行排序的任务。

在终端形式中,事实证明,数据节点向主节点发送垃圾邮件,导致主节点进入 Full GC。 之后,我们的主角色转移到下一个节点,同样的事情发生在它身上,结果集群完全崩溃了。

我们进行了测量,在 6.4.0 版本之前(该问题已修复),我们只需同时输出 10 个数据节点中的 360 个数据节点即可完全关闭集群。

它看起来像这样:

Elasticsearch 集群 200 TB+

在修复了这个可怕的错误的 6.4.0 版本之后,数据节点不再杀死主节点。 但这并没有让他变得“更聪明”。 即:当我们输出 2、3 或 10 个(除 XNUMX 之外的任何数量)数据节点时,主节点会收到一些第一条消息,表明节点 A 已离开,并尝试将此信息告诉节点 B、节点 C、节点 D。

目前,只能通过设置尝试告诉某人某件事的超时时间(大约为 20-30 秒)来解决此问题,从而控制数据中心移出集群的速度。

原则上,这符合最初作为项目一部分向最终产品提出的要求,但从“纯科学”的角度来看,这是一个错误。 顺便说一句,开发人员在 7.2 版本中成功修复了这个问题。

而且,当某个数据节点发生故障时,事实证明,传播有关其退出的信息比告诉整个集群上存在这样那样的主分片(以便在另一个数据中提升副本分片)更重要。中心在初级,并且信息可以写在它们上)。

因此,当一切都已经平息下来时,释放的数据节点不会立即被标记为过时。 因此,我们被迫等待,直到所有对已释放数据节点的 ping 超时,只有在那之后,我们的集群才开始告诉我们,我们需要继续记录信息。 您可以阅读更多相关内容 这里.

所以今天高峰时段撤数据中心的操作大约需要5分钟。 对于这样一个庞大而笨拙的庞然大物来说,这已经是一个相当不错的结果了。

因此,我们做出以下决定:

  • 我们有 360 个数据节点和 700 GB 磁盘。
  • 60 个协调器,用于通过这些相同的数据节点路由流量。
  • 从 40 之前的版本开始,我们留下了 6.4.0 个 master 作为遗产 - 为了在数据中心撤出后生存下来,我们做好了失去几台机器的心理准备,以保证即使在最坏的情况
  • 任何在一个容器上组合角色的尝试都会遇到这样的事实:节点迟早会在负载下崩溃。
  • 整个集群使用 31 GB 的 heap.size:所有减小大小的尝试都导致要么在使用前导通配符的繁重搜索查询中杀死一些节点,要么在 Elasticsearch 本身中得到断路器。
  • 另外,为了保证搜索性能,我们尽量保持集群中对象的数量尽可能少,以便在master遇到的瓶颈中处理尽可能少的事件。

最后关于监控

为了确保这一切按预期进行,我们监控以下内容:

  • 每个数据节点都会向我们的云端报告它的存在,并且上面有这样那样的分片。 当我们在某处消灭某些东西时,集群会在 2-3 秒后报告说,在中心 A 我们消灭了节点 2、3 和 4 - 这意味着在其他数据中心,我们在任何情况下都无法消灭那些只有一个分片的节点左边。
  • 了解了 master 行为的本质后,我们非常仔细地查看待处理任务的数量。 因为即使是一个卡住的任务,如果没有及时超时,理论上在某些紧急情况下也可能成为主节点中副本分片升级不起作用的原因,这就是索引将停止工作的原因。
  • 我们还非常仔细地关注垃圾收集器延迟,因为我们在优化过程中已经遇到了很大的困难。
  • 通过线程拒绝,提前了解瓶颈在哪里。
  • 嗯,标准指标,如堆、RAM 和 I/O。

在构建监控时,必须考虑到Elasticsearch中Thread Pool的特性。 Elasticsearch 文档 描述了搜索和索引的配置选项和默认值,但是完全没有提到thread_pool.management,这些线程处理,特别是像_cat/shards和其他类似的查询,在编写监控时使用起来很方便。 集群越大,单位时间内执行的此类请求就越多,而前面提到的thread_pool.management不仅官方文档中没有介绍,而且默认限制为5个线程,很快就被处理掉了哪个监控停止正常工作。

最后我想说的是:我们做到了! 我们能够为程序员和开发人员提供一种工具,该工具几乎在任何情况下都可以快速可靠地提供有关生产中发生的情况的信息。

是的,事实证明它相当复杂,但是,尽管如此,我们还是设法将我们的愿望融入到现有产品中,而我们不必自己修补和重写。

Elasticsearch 集群 200 TB+

来源: habr.com

添加评论