Elasticsearch 用于支持我们的主要媒体监控应用,客户通过该应用可以检索和分析媒体数据,比如新闻文章、(公开的)Facebook 帖子、Instagram 帖子、博客和微博。我们通过使用一个混合 API 来收集这些内容,并爬取和稍作加工,使得它们可被 Elasticsearch 检索到。
Elasticsearch 用于支持我们的主要媒体监控应用,客户通过该应用可以检索和分析媒体数据,比如新闻文章、(公开的)Facebook 帖子、Instagram 帖子、博客和微博。我们通过使用一个混合 API 来收集这些内容,并爬取和稍作加工,使得它们可被 Elasticsearch 检索到。 本文将分享我们所学到的经验、如何调优 Elasticsearch,以及要绕过的一些陷阱。 数据量 每天都有数量相当庞大的新闻和微博产生;在高峰期需要索引大约 300 多万社论文章,和近 1 亿条社交帖子数据。其中社论数据长期保存以供检索(可回溯到 2009 年),社交帖子数据保存近 15 个月的。当前的主分片数据使用了大约 200 TB 的磁盘空间,副本数据大约 600 TB。 我们的业务每分钟有 3 千次请求。所有的请求通过一个叫做“search-service”的服务,该服务会依次完成所有与 Elasticsearch 集群的交互。大部分检索规则比较复杂,包括在面板和新闻流中。比如,一个客户可能对 Tesla 和 Elon Musk 感兴趣,但希望排除所有关于 SpaceX 或 PayPal 的信息。用户可以使用一种与 Lucene 查询语法类似的灵活语法,如下:
我们最长的此类查询有 60 多页。重点是:除了每分钟 3 千次请求以外,没有一个查询是像在 Google 里查询“Barack Obama”这么简单的;这简直就是可怕的野兽,但 ES 节点必须努力找出一个匹配的文档集。 版本 我们运行的是一个基于 Elasticsearch 1.7.6 的定制版本。该版本与 1.7.6 主干版本的唯一区别是,我们向后移植(backport)了 roaring bitsets/bitmaps 作为缓存。该功能是从 Lucene 5 移植到 Lucene 4 的,对应移植到了 ES 1.X 版本。Elasticsearch 1.X 中使用默认的 bitset 作为缓存,对于稀疏结果来说开销非常大,不过在 Elasticsearch 2.X 中已经做了优化。 为何不使用较新版本的 Elasticsearch 呢?主要原因是升级困难。在主版本间滚动升级只适用于从 ES 5 到 6(从 ES 2 到 5 应该也支持滚动升级,但没有试过)。因此,我们只能通过重启整个集群来升级。宕机对我们来说几乎不可接受,但或许可以应对一次重启所带来的大约 30-60 分钟宕机时间;而真正令人担心的,是一旦发生故障并没有真正的回滚过程。 截止目前我们选择了不升级集群。当然我们希望可以升级,但目前有更为紧迫的任务。实际上该如何实施升级尚未有定论,很可能选择创建另一个新的集群,而不是升级现有的。 节点配置 我们自 2017 年 6 月开始在 AWS 上运行主集群,使用 i3.2xlarge 实例作为数据节点。之前我们在 COLO(Co-located Data Center)里运行集群,但后续迁移到了 AWS 云,以便在新机器宕机时能赢得时间,使得我们在扩容和缩容时更加弹性。 我们在不同的可用区运行 3 个候选 master 节点,并设置 discovery.zen.minimum_master_nodes 为 2。这是避免脑裂问题 split-brain problem 非常通用的策略。 我们的数据集在存储方面,要求 80% 容量和 3 个以上的副本,这使得我们运行了 430 个数据节点。起初打算使用不同层级的数据,在较慢的磁盘上存储较旧的数据,但是由于我们只有相关的较低量级旧于 15 个月的数据(只有编辑数据,因为我们丢弃了旧的社交数据),然而这并未奏效。每个月的硬件开销远大于运行在 COLO 中,但是云服务支持扩容集群到 2 倍,而几乎不用花费多少时间。 你可能会问,为何选择自己管理维护 ES 集群。其实我们考虑过托管方案,但最后还是选择自己安装,理由是: AWS Elasticsearch Service 暴露给用户的可控性太差了, Elastic Cloud 的成本比直接在 EC2 上运行集群要高 2-3 倍。 为了在某个可用区宕机时保护我们自身,节点分散于 eu-west-1 的所有 3 个可用区。我们使用 AWS plugin 来完成该项配置。它提供了一个叫做 aws_availability_zone 的节点属性,我们把 cluster.routing.allocation.awareness.attributes 设置为 aws_availability_zone。这保证了 ES 的副本尽可能地存储在不同的可用区,而查询尽可能被路由到相同可用区的节点。 这些实例运行的是 Amazon Linux,临时挂载为 ext4,有约 64GB 的内存。我们分配了 26GB 用于 ES 节点的堆内存,剩下的用于磁盘缓存。为何是 26GB?因为 JVM 是在一个黑魔法之上构建的。 我们同时使用 Terraform 自动扩容组来提供实例,并使用 Puppet 完成一切安装配置。 索引结构 因为我们的数据和查询都是基于时间序列的,所以使用了 time-based indexing,类似于 ELK (elasticsearch, logstash, kibana) stack。同时也让不同类型的数据保存在不同的索引库中,以便诸如社论文档和社交文档类数据最终位于不同的每日索引库中。这样可以在需要的时候只丢弃社交索引,并增加一些查询优化。每个日索引运行在两个分片中的一个。 该项设置产生了大量的分片(接近 40k)。有了这么多的分片和节点,集群操作有时变得更特殊。比如,删除索引似乎成为集群 master 的能力瓶颈,它需要把集群状态信息推送给所有节点。我们的集群状态数据约 100 MB,但通过 TCP 压缩可减少到 3 MB(可以通过 curl localhost:9200/_cluster/state/_all 查看你自己集群的状态数据)。Master 节点仍然需要在每次变更时推送 1.3 GB 数据(430 节点 x 3 MB 状态大小)。除了这 1.3 GB 数据外,还有约 860 MB 必须在可用区(比如 最基本的通过公共互联网)之间传输。这会比较耗时,尤其是在删除数百个索引时。我们希望新版本的 Elasticsearch 能优化这一点,首先从 ES 2.0 支持仅发送集群状态的差分数据这一特性开始。 性能 如前所述,我们的 ES 集群为了满足客户的检索需求,需要处理一些非常复杂的查询。 为应对查询负载,过去几年我们在性能方面做了大量的工作。我们必须尝试公平分享 ES 集群的性能测试,从下列引文就可以看出。 不幸的是,当集群宕机的时候,不到三分之一的查询能成功完成。我们相信测试本身导致了集群宕机。—— 摘录自使用真实查询在新 ES 集群平台上的第一次性能测试 为了控制查询执行过程,我们开发了一个插件,实现了一系列自定义查询类型。通过使用这些查询类型来提供 Elasticsearch 官方版本不支持的功能和性能优化。比如,我们实现了 phrases 中的 wildcard 查询,支持在 SpanNear 查询中执行;另一个优化是支持“*”代替 match-all-query;还有其他一系列特性。 Elasticsearch 和 Lucene 的性能高度依赖于具体的查询和数据,没有银弹。即便如此,仍可给出一些从基础到进阶的参考:
图表说明:响应时间。有 / 没有 重写 Lucene 查询执行。同时也表明不再有节点每天发生多次内存不足的情况。 顺便说明下,因为我知道会面临一个问题:从上一次性能测试我们知道通过升级到 ES 2.X 能小幅提升性能,但是并不能改变什么。话虽如此,但如果你已经从 ES 1.X 集群迁移到了 ES 2.X,我们很乐意听取关于你如何完成迁移的实践经验。 |
|