扩容并不是无限的edit

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

集群状态(cluster state) 是一种数据结构,存储下列集群级别的信息:

  • 集群级别的设置 (cluster-level settings)
  • 集群中的节点 (nodes that are part of the cluster)
  • 索引以及它们的设置(setting)、映射(mapping)、分析器(analyzer)、预热器(warmer)和别名(alias)
  • 与每个索引关联的分片以及它们分配到的节点

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

GET /_cluster/state

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

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

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

但是,请记住,这个相同的数据结构必须存在于每个节点的内存中,并且必须在更新时发布到每个节点。数据量越大,这个过程就越长。

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

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

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

更好的方式是使用嵌套对象(nested objects), 它使用一个字段作为参数名referer,另一个字段作为关联的值count

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

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

最终,不管你的初衷有多好,你可能会发现集群节点数量、索引、映射对于一个集群来说还是太大了。 在这个阶段,将问题拆分到多个集群中可能是值得的。得益于​tribe节点​, 你甚至可以跨多个集群进行搜索,就好像它们是一个巨大的集群。