0%

[译]Elasticsearch自定义文档路由

注意:原文发表时间是13年,所以实现有可能与新版不一致.
原文地址:https://www.elastic.co/cn/blog/customizing-your-document-routing

什么是路由

您的所有数据都存在于集群中某个位置的主分片中。您可能有五个分片或五百个,但任何特定的文档都只位于其中一个。路由是确定文档将保存到哪个分片中的过程。

因为Elasticsearch努力让90%的用户使用默认值,所以路由是自动处理的。对于大多数用户来说,文档存储在哪里并不重要。

默认的路由方案对文档的ID进行hash,并使用它来查找分片。这包括用户提供的ID和Elasticsearch随机生成的ID。默认路由使文档在整个分片集中均匀分布 - 不会出现文档集中倾斜到某个分片上形成热点。

自定义路由试用的场景

随机路由在大多数情况下都运行良好,但在某些情况下,可以通过自定义路由带来更好的性能。想象一个包含 20 个分片的索引(过度分配以支持未来的增长)。

当在集群上执行搜索请求时会发生什么?

  • 搜索请求命中节点
  • 节点将此请求广播到索引中的每个分片(主分片或副本分片)
  • 每个分片执行搜索查询并返回结果
  • 结果在网关节点上合并、排序并返回给用户

Elasticsearch 不知道在哪里查找您的文档。所有文档都随机分布在您的集群中……因此 Elasticsearch 别无选择,只能将请求广播到所有 20 个分片。这是不可忽略的开销,并且可能会影响性能。

如果我们能够告诉 Elasticsearch 该文档位于哪个分片中,岂不是很好? 然后您只需搜索一个分片即可找到您需要的文档。

这正是自定义路由的作用。

您不需要盲目地向所有分片广播,而是告诉 Elasticsearch“嘿! 搜索这个分片上的数据! 一切都在那里,我保证!” 例如,您可以根据文档的 user_id 路由文档。或者他们的邮政编码。或者您的应用程序中经常搜索/过滤的任何内容。

路由确保具有相同路由值的所有文档都将定位到同一个分片,从而无需广播搜索。

自定义路由速度很快

自定义路由可确保仅查询一个分片。

如果您的问题适合自定义路由,那么这有可能显着提高性能。

无论索引中有 20 个还是 100 个分片,自定义路由都可以确保只查询保存数据的分片。

配置自定义路由

使用自定义路由时,每当索引、获取、删除或更新文档时提供路由值非常重要。

忘记路由值可能会导致文档在多个分片上建立索引。作为保护措施,可以配置 _routing 字段来创建所有 CRUD 操作所需的自定义路由值:

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT my-index-000002
{
"mappings": {
"_routing": {
"required": true //文章所有操作都需要指定路由
}
}
}

PUT my-index-000002/_doc/1
{
"text": "No routing value provided"
}

原文已经过时了,所有没有翻译原文;
这里是翻译自官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html

自定义路由搜索

自定义路由可以减少搜索的影响。不必将搜索请求发送到索引中的所有分片,而是将请求仅发送到与特定路由值(或多个值)匹配的分片:

1
2
3
4
5
6
7
8
GET my-index-000001/_search?routing=user1,user2 
{
"query": {
"match": {
"title": "document"
}
}
}

此搜索请求将仅在与 user1 和 user2 路由值关联的分片上执行。

注意事项

在使用自定义路由时,您应该注意以下几个问题。

Parent/Child/Grandchild schemes

出于性能原因,父/子映射可确保所有子分片都路由到与父分片相同的分片。在内部,Elasticsearch 将子级的路由值设置为等于父级的 ID,确保每个人都位于同一个分片。

但是,如果您添加第三层(孙子),父/子映射就会失败。查看这些示例文档:

1
2
3
curl -XPUT localhost:9200/parentchild/product/Product001 -d '{...}'
curl -XPUT localhost:9200/parentchild/vendors/VendorABC?parent=Product001 -d '{...}'
curl -XPUT localhost:9200/parentchild/vendordetails/LocationXYZ?parent=VendorABC -d '{...}'

相当简单的嵌套关系。产品的子代被称为“供应商”,而这些产品则有自己的子代,称为“VendorDetails”。那么,为什么这不起作用呢?让我们看看派生的路由值:

Doc ID Parent Routing Value
Product001 - Product001
VendorABC Product001 Product001
LocationXYZ VendorABC VendorABC

正如您所看到的,文档将自动使用其父级的ID进行路由。VendorABC使用Product001(到目前为止很好),但LocationXYZ使用VendorABC(坏)。这意味着数据没有被正确地并置。

解决方案很简单:告诉孙子(以及任何后续的曾孙)它的路由值应该是祖父母的ID。在内部,Elasticsearch将优先选择路由参数而不是父参数:

1
2
3
curl -XPUT localhost:9200/parentchild/product/Product001 -d '{...}'
curl -XPUT localhost:9200/parentchild/vendors/VendorABC?parent=Product001 -d '{...}'
curl -XPUT localhost:9200/parentchild/vendordetails/LocationXYZ?parent=VendorABC&routing=Product001 -d '{...}'
Doc ID Parent Routing Value
Product001 - Product001
VendorABC Product001 Product001
LocationXYZ VendorABC Product001

热点

当Elasticsearch管理路由时,它可以确保所有碎片的分布相当均匀。然而,一旦您开始实现自己的自定义方案,就完全有可能失去这种一致性。假设您正在按userID进行路由。你的大多数用户都很小,只有少量的文档……但偶尔你会遇到一个拥有数百万的怪物用户。

自定义路由将确保MonsterUser的所有文档都指向一个shard,而不是在集群中均匀分布。这对性能有好处——搜索会很快执行。

但是,如果MonsterUser2和MonsterUser3也被分配到同一个碎片,会发生什么?现在,在一个碎片上有三个大用户,而其余的碎片只加载了少量。

情况不太好。这些类型的场景可以而且确实会发生。针对这些“数据热点”的最佳防御措施是手动识别大用户(或其他急需性能的用户),并将其拆分为自己的索引。然后,您可以设置一个别名,使分离对应用程序透明。

现在,您可以两全其美:自定义路由将大多数用户沙盒在一个碎片中,而大用户则被拆分为自己的索引和一组碎片。

You can check-out any time you like, but you can never leave!

一旦在索引文档时指定了自定义路由,无论何时更新或删除文档,都必须继续指定。您已经从Elasticsearch获得了一些控制权,如果您决定在某个时候停止使用自定义路由,则没有简单的方法可以恢复此控制权。唯一的方法是重新建立数据索引(不指定自定义路由)。

结论

自定义路由是一个强大的功能,可以在特定情况下提高性能。虽然默认路由对大多数人来说已经足够了,但有时您只需要对文档的放置进行更多的控制。

欢迎关注我的其它发布渠道