ES(四)路由与多索引

ES有路由的和多索引的功能,都用在什么场景下?stackoverflow上的一个问题和我的疑问很像:

I’m planning a strategy for querying millions of docs in date and user directions.

  • Option 1 - indexing by user. routing by date.
  • Option 2 - indexing by date. routing by user. What are the differences or advantages when using routing or indexing?

ES中的分索引与路由对应Mysql中是什么?

举一个Mysql的海量订单下的场景,订单至少有用户id与时间两个维度,数据库如何设计?肯定是分表、分库了,按按照什么维度区分?一般(指一般的使用场景,下文举例的场景可以认为是一般的场景)是先按照时间的维度进行区分,时间维度内,在按照用户id的维度去分。一个很明显的好处就是,物理存储上数据会按照时间维度聚集在一起,一般会有个保存多长时间的参数,例如聊天记录这东西,数据按照时间的维度物理上聚集在一起,在清理记录(或不清理只是不让用户查询到retired记录)的时候就非常方便,因为物理上是在一起,直接把这个分库关掉就可以了。但如果是按照用户的维度,那可能需要操作分散在不同的地方的多个库,效率显然会低。

上面的场景对于ES一样,当然还有其他的原因,下面会详细解析。这里只是要说明ES的“分索引”其实很像Mysql的分库,而ES的“路由”功能,Mysql没有机制支持,但是可以通过其他方式实现,如订单ID中加入userid,然后再分表的时候,可以根据usrid计算出分表的地址,直接定位到特定的表。或者更常见的一种方式,订单ID中包含分库分表的ID,然后查找的时候按照这分库分表的ID进行查找,这就是“Mysql的路由机制”。

ES中的分索引

底层来看,ES的每个shard都是一个单独的index,这多索引对于ES而言就是不是什么新奇的特性,因为用户的角度看,建立的每个index其实都是多个index(shard)组成的。多个索引的特性只是在多封装一点点而且,代码层上看,支持这个特性的代码99%是复用的。如何使用多索引可以参考官网:Multiple IndicesSearch,Multi-Index, Multi-Type

ES中的路由

路由这个特性虽然说Mysql可以模拟来实现,但是也是学了一部分,因为有事务的约束,数据的存放位置上不可能那么灵活。而ES也是一种Nosql数据库,数据是没有事务什么约束的,而且shard机制本来就是有默认的hash算法,插入一条数据的时候会按照文档的id进行hash,文档在多个shards上均匀分布的。但是这样,进行查询的时候就有如下步骤(参考:customizing-your-document-routing ):

  1. Search request hits a node
  2. The node broadcasts this request to every shard in the index (either a primary or replica shard)
  3. Each shard performs the search query and responds with results
  4. Results are merged on the gateway node, sorted and returned to the user

因为有了任务的广播,所有的node执行然后合并的过程,如果任务真的是需要所有节点提供那还好,但是如果查询的数据本来可以在一个shard上,但是由于hash算法给分散开来,这样的查询,显然效率不高。所以,这个ES的hash算法就可以用来做文章,可以由用户指定,例如使用userid。这样,一个user的查询可能只在一个shard上,而其他的节点并不需要参与查询,这样,一般来说,速度更快。

但是,上述说的场景有个前提,就是有多个user,对于ES来说,什么是user呢?如ES常用的日志搜索场景,每个单独的系统可以看作一个user,这样按照user路由,然后命中其中的一个shard进行计算,速度一般会快。

每次查询都路由到一个shard就会快?当然还有个前提,这个shard能够存下这个user的数据,还有所有就是系统中不存在热点问题。当然对于这个问题,已有现成的解决方案。如果这个user的数据量非常大,而且查询非常频繁,那么可以针对这个user做个特殊的路由,直接路由到一个新的index,这索引可能有更多更牛的机器,来处理这个不同的user。

最后一点,路由的选择肯定是要根据业务的,所以有个特点,一旦选取上,根据特定的条件(带路由)查询,速度可能会很快,但是如果查询没有路由信息,那就没什么优势了。这与Hbase的行键设计很像,设计完成后按照行键里面有的参数进行查询速度ok,但是一旦行键用不上,同样会全表扫描。如果场景中,需要有两个路由(不是联合的路由,就像Mysql不是联合主键),可以忽视其中一个路由,但是如果两种查询响应和吞吐量都很高,那就只能把一个集群分裂成两个集群,两个集群数据是一样的,但因为路由不同,数据的分布方式是不一样的。

分索引与路由的一般使用场景?

回答文章开头的问题,直接引用stackoverflow中的答案:

One of the design patterns that Shay Banon @ Elasticsearch recommends is: index by time range, route by user and use aliasing.

Some of the advantages of this approach:

  1. if you search using aliases for users, you hit only shards where the users’ data resides
    • if a user’s data grows, you could consider creating a separate index for that user (all you need is to point that user’s alias to the new index)
    • no performance implications of over allocation of shards
    • you could ‘retire’ older logs by simply closing (when you close indices, they consume practically no resources) or deleting an entire index (deleting an index is simpler than deleting documents within an index)

一共四点,第一点和第四点上面有提到过。

第二点也提到过一点,但是侧重点不同,这里说的是某些user从没什么特别到所拥有的数据持续增长的情况,对于这种突然变化的场景,使用userid进行路由是能够容易进行处理的。

第三点是没有过多的shards,这是相对于按照时间进行路由,userid进行分索引的情况,主要是分索引这里,这userid分索引,也麻烦,如果不用一致性hash算法,到底开始分多少索引?用户增多的情况如何增加索引数目?如果是Redis这样预分片的策略,会造成”over allocation of shards”不知道自己理解的是不是作者想表达的意思,但是肯定会存在这样的问题。

使用路由后ES的shard策略

本质上没有改变,都是:

shard_num = hash(_routing) % num_primary_shards

但是,这个_routing的值是相当于指定的:

The routing value is an arbitrary string, which defaults to the document’s _id but can also be set to a custom value.

Multiple Indices
Search,Multi-Index, Multi-Type
elasticsearch - routing VS. indexing for query performance
_routing field
customizing-your-document-routing

亿级规模的Elasticsearch优化实战

Table of Contents