本节书摘来自华章计算机《深入理解ElasticSearch》一书中的第2章,第2.8节,作者:[美] 拉斐尔·酷奇(Rafa Ku) 马雷克·罗戈任斯基(Marek Rogoziński)更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.8 ElasticSearch切面机制中的过滤器与作用域
当使用ElasticSearch的切面机制时,有几件事情需要注意。首先要记住的是,系统只在查询结果之上计算切面结果。如果你在filter对象内部且在query对象外部包含了过滤器,那么这些过滤器将不会对参与切面计算的文档产生影响。另外一个需要注意的事情是作用域(scope),它能扩充用于切面计算的文档。接下来我们来看几个范例。
2.8.1 范例数据
先回忆一下查询、过滤器、切面是如何一起工作的。为了演示该功能,我们先使用下面的命令索引一些文档至索引books中:
2.8.2 切面计算和过滤
让我们查看一下当使用查询和过滤器的时候,切面计算是如何工作的。我们先执行一个简单的查询,该查询返回索引books中的所有文档。为了缩小返回结果集,过程中使用了一个过滤器,用来限制查询只返回category字段值为book的文档。此外,我们还在price字段上使用了一个简单的基于范围的切面计算,用来查看有多少书籍的price低于30,多少书籍的price高于30。整个查询如下所示(代码存储在query_with_filter.json中):
查询执行以后,将得到下面的返回结果:
https://yqfile.alicdn.com/fb4ade5849274215ca4ac46301dfc64015e44639.png" >
尽管查询被限制为只返回category字段值为book的文档,但是对切面计算来说并不是这样。事实上,切面作用于books索引的所有文档(因为使用了match_all查询)。因此,读者需要意识到ElasticSearch的切面在进行计算时并不考虑过滤器的因素。当过滤器作为是查询的一部分时,例如使用filtered查询类型时,将会发生哪些动作呢?我们来进一步探究。
2.8.3 过滤器作为查询的一部分
再次尝试前面的例子,只是这次使用过滤查询类型。于是,我们再次结合filtered检索所有category字段值为book的文档,并且在price字段上使用了一个简单的基于范围的切面计算,以查看有多少书籍的价格高于30以及多少书籍的价格低于30。为了实现这个功能,我们执行下面这个查询(代码存储在filtered_query.json中):
该查询返回结果如下所示:
https://yqfile.alicdn.com/a4457b359f5e084c2718239860fd52d58ff4f8f6.png" >
https://yqfile.alicdn.com/bfb4a15dd9ac2d708e675460ebb0235432b1d398.png" >
正如你所见,切面计算作用于查询返回结果上,且跟事前预期的结果一样,这正是因为过滤器成为了查询的一部分!在我们的案例中,切面计算结果包含两个范围,每个范围只有一个文档。
2.8.4 切面过滤器
考虑一下,如果只想为title字段包含词项2的书籍计算分组,我们应该怎么做。如果在查询使用第二个过滤器,虽然能减少查询返回结果,但这并不是我们想要的。一种更明智的做法是使用切面过滤器(facet filter)。
在切面类型的相同层级上使用facet_filter过滤器,能通过使用过滤器减少计算切面时的文档数,就像在查询时使用过滤器那样。例如,如果用户想通过使用facet_filter将范围切面计算过滤成只包含title字段值为2的文档,那么需要将查询中切面计算相关部分修改为下面这样(完整的查询保存在filtered_query_facet_filter.json中):
https://yqfile.alicdn.com/7e46c87f6ab9c6e4b88c56884999f82ed7bb728d.png" >
正如你所见,我们新引入了一个名为term的简单过滤器。修改后的查询的返回结果看起来与下面的类似:
https://yqfile.alicdn.com/b275dbe87e0b3683299e66ffdb97b9f7a63cd9f2.png" >
查看第一个返回结果,你就能发现不同之处。通过在查询中使用切面过滤器,虽然能限制切面计算只作用于单个文档,但我们的查询仍然能返回两个文档。
2.8.5 全局作用域
如果我们想查询所有书名中包含词项2的文档,但同时又要显示索引中所有文档的基于范围的切面计算结果,应该如何处理呢?幸运的是,此处并不需要强制运行第二个查询,这是因为可以使用全局切面作用域(global faceting scope)来达成目的,并具体通过将切面类型的global属性配置为true来实现。
例如,修改我们前面用过的第一个查询。在本小节,我们并不包含过滤器,取而代之的是一个词项查询。除此之外,还需添加global属性,从而查询看起来与下面的查询类似(该查询保存在query_global_scope.json中):
https://yqfile.alicdn.com/63f854166aed1dfaa5d6d9016c4c29dd3414849d.png" >
现在,查看该查询的返回结果:
https://yqfile.alicdn.com/0d9d55a17a14d7b660cc930c6683578464f60d63.png" >
尽管查询只返回了两个文档,但后面依然对索引中所有文档进行了切面计算,这归功于查询中的global属性。
我们再来看一个关于global属性的使用案例,即展示基于切面计算的导航。想象一下这种场景:在用户输入他的查询之后显示一个顶级导航,例如,使用基于词项的切面计算来枚举所有电子商务网站的顶级目录。这种案例中global作用域就显得尤为有用了。