扩容并不是无限的

贯彻整个章节我们讨论了多种 Elasticsearch 可以做到的扩容方式。大多数的扩容问题可以通过添加节点来解决。但有一种资源是有限制的,因此值得我们认真对待:集群状态。

集群状态 是一种数据结构,贮存下列集群级别的信息:

  • 集群级别的设置
  • 集群中的节点
  • 索引以及它们的设置、映射、分析器、预热器(Warmers)和别名
  • 与每个索引关联的分片以及它们分配到的节点

你可以通过如下请求查看当前的集群状态:

  1. GET /_cluster/state

集群状态存在于集群中的每个节点,包括客户端节点。这就是为什么任何一个节点都可以将请求直接转发至被请求数据的节点——每个节点都知道每个文档应该在哪里。

只有主节点被允许更新集群状态。想象一下一个索引请求引入了一个之前未知的字段。持有那个文档的主分片所在的节点必须将新的映射转发到主节点上。主节点把更改合并到集群状态中,然后向所有集群中的所有节点发布一个新的版本。

搜索请求 使用 集群状态,但它们不会产生修改。同样,文档级别的增删改查请求也不会对集群状态产生修改。当然,除非它们引入了一个需要更新映射的新的字段了。总的来说,集群状态是静态的不会成为瓶颈。

然而,需要记住的是相同的数据结构需要在每个节点的内存中保存,并且当它发生更改时必须发布到每一个节点。集群状态的数据量越大,这个操作就会越久。

我们见过最常见的集群状态问题就是引入了太多的字段。一个用户可能会决定为每一个 IP 地址或者每个 referer URL 使用一个单独的字段。下面这个例子通过为每一个唯一的 referer 使用一个不同的字段名来保持对页面浏览量的计数:

  1. POST /counters/pageview/home_page/_update
  2. {
  3. "script": "ctx._source[referer]++",
  4. "params": {
  5. "referer": "http://www.foo.com/links?bar=baz"
  6. }
  7. }

这种方式十分的糟糕!它会生成数百万个字段,这些都需要被存储在集群状态中。 每当见到一个新的 referer ,都有一个新的字段需要加入那个已经膨胀的集群状态中,这都需要被发布到集群的每个节点中去。

更好的方式是使用nested objects,它使用一个字段作为参数名—referer—另一个字段作为关联的值—count

  1. "counters": [
  2. { "referer": "http://www.foo.com/links?bar=baz", "count": 2 },
  3. { "referer": "http://www.linkbait.com/article_3", "count": 10 },
  4. ...
  5. ]

这种嵌套的方式有可能会增加文档数量,但 Elasticsearch 生来就是为了解决它的。重要的是保持集群状态小而敏捷。

最终,不管你的初衷有多好,你可能会发现集群节点数量、索引、映射对于一个集群来说还是太大了。此时,可能有必要将这个问题拆分到多个集群中了。感谢​{ref}/modules-tribe.html[tribe nodes]​,你甚至可以向多个集群发出搜索请求,就好像我们有一个巨大的集群那样。