使用filters优化查询

ElasticSearch支持多种不同类型的查询方式,这一点大家应该都已熟知。但是在选择哪个文档应该匹配成功,哪个文档应该呈现给用户这一需求上,查询并不是唯一的选择。ElasticSearch 查询DSL允许用户使用的绝大多数查询都会有各自的标识,这些查询也以嵌套到如下的查询类型中:

  • constant_score
  • filterd
  • custom_filters_score

那么问题来了,为什么要这么麻烦来使用filtering?在什么场景下可以只使用queries? 接下来就试着解决上面的问题。

过滤器(Filters)和缓存

首先,正如读者所想,filters来做缓存是一个很不错的选择,ElasticSearch也提供了这种特殊的缓存,filter cache来存储filters得到的结果集。此外,缓存filters不需要太多的内存(它只保留一种信息,即哪些文档与filter相匹配),同时它可以由其它的查询复用,极大地提升了查询的性能。设想你正运行如下的查询命令:

{
"query" : {
"bool" : {
"must" : [
{
"term" : { "name" : "joe" }
},
{
"term" : { "year" : 1981 }
}
]
}
}
}

该命令会查询到满足如下条件的文档:name域值为joe同时year域值为1981。这是一个很简单的查询,但是如果用于查询足球运动员的相关信息,它可以查询到所有符合指定人名及指定出生年份的运动员。

如果用上面命令的格式构建查询,查询对象会将所有的条件绑定到一起存储到缓存中;因此如果我们查询人名相同但是出生年份不同的运动员,ElasticSearch无法重用上面查询命令中的任何信息。因此,我们来试着优化一下查询。由于一千个人可能会有一千个人名,所以人名不太适合缓存起来;但是年份比较适合(一般year域中不会有太多不同的值,对吧?)。因此我们引入一个不同的查询命令,将一个简单的query与一个filter结合起来。

{
"query" : {
"filtered" : {
"query" : {
"term" : { "name" : "joe" }
},
"filter" : {
"term" : { "year" : 1981 }
}
}
}
}

我们使用了一个filtered类型的查询对象,查询对象将query元素和filter元素都包含进去了。第一次运行该查询命令后,ElasticSearch就会把filter缓存起来,如果再有查询用到了一样的filter,就会直接用到缓存。就这样,ElasticSearch不必多次加载同样的信息。

并非所有的filters会被默认缓存起来

缓存很强大,但实际上ElasticSearch在默认情况下并不会缓存所有的filters。这是因为部分filters会用到域数据缓存(field data cache)。该缓存一般用于按域值排序和faceting操作的场景中。默认情况下,如下的filters不会被缓存:

  • numeric_range
  • script
  • geo_bbox
  • geo_distance
  • geo_distance_range
  • geo_polygon
  • geo_shape
  • and
  • or
  • not

尽管上面提到的最后三种filters不会用到域缓存,它们主要用于控制其它的filters,因此它不会被缓存,但是它们控制的filters在用到的时候都已经缓存好了。

更改ElasticSearch缓存的行为

ElasticSearch允许用户通过使用_chache和_cache_key属性自行开启或关闭filters的缓存功能。回到前面的例子,假定我们将关键词过滤器的结果缓存起来,并给缓存项的key取名为year_1981_cache,则查询命令如下:

{
"query" : {
"filtered" : {
"query" : {
"term" : { "name" : "joe" }
},
"filter" : {
"term" : {
"year" : 1981,
"_cache_key" : "year_1981_cache"
}
}
}
}
}

也可以使用如下的命令关闭该关键词过滤器的缓存:

{
"query" : {
"filtered" : {
"query" : {
"term" : { "name" : "joe" }
},
"filter" : {
"term" : {
"year" : 1981,
"_cache" : false
}
}
}
}
}

为什么要这么麻烦地给缓存项的key取名

上面的问题换个说法就是,我有是否有必要如此麻烦地使用_cache_key属性,ElasticSearch不能自己实现这个功能吗?当然它可以自己实现,而且在必要的时候控制缓存,但是有时我们需要更多的控制权。比如,有些查询复用的机会不多,我们希望定时清除这些查询的缓存。如果不指定_cache_key,那就只能清除整个过滤器缓存(filter cache);反之,只需要执行如下的命令即可清除特定的缓存:

curl -XPOST 'localhost:9200/users/_cache/clear?filter_keys=year_1981_cache'

什么时候应该改变ElasticSearch 过滤器缓存的行为

当然,有的时候用户应该更多去了解业务需求,而不是让ElasticSearch来预测数据分布。比如,假设你想使用geo_distance 过滤器将查询限制到有限的几个地理位置,该过滤器在请多查询请求中都使用着相同的参数值,即同一个脚本会在随着过滤器一起多次使用。在这个场景中,为过滤器开启缓存是值得的。任何时候都需要问自己这个问题“过滤器会多次重复使用吗?”添加数据到缓存是个消耗机器资源的操作,用户应避免不必要的资源浪费。

关键词查找过滤器

缓存和标准的查询并不是全部内容。随着ElasticSearch 0.90版本的发布,我们得到了一个精巧的过滤器,它可以用来将多个从ElasticSearch中得到值作为query的参数(类似于SQL的IN操作)。

让我们看一个简单的例子。假定我们有在一个在线书店,存储了用户,即书店的顾客购买的书籍信息。books索引很简单(存储在books.json文件中):

{
"mappings" : {
"book" : {
"properties" : {
"id" : { "type" : "string", "store" : "yes", "index" :
"not_analyzed" },
"title" : { "type" : "string", "store" : "yes", "index" :
"analyzed" }
}
}
}
}

上面的代码中,没有什么是非同寻常的;只有书籍的id和标题。 接下来,我们来看看clients.json文件,该文件中存储着clients索引的mappings信息:

{
"mappings" : {
"client" : {
"properties" : {
"id" : { "type" : "string", "store" : "yes", "index" :
"not_analyzed" },
"name" : { "type" : "string", "store" : "yes", "index" :
"analyzed" },
"books" : { "type" : "string", "store" : "yes", "index" :
"not_analyzed" }
}
}
}
}

索引定义了id信息,名字,用户购买书籍的id列表。此外,我们还需要一些样例数据:

curl -XPUT 'localhost:9200/clients/client/1' -d '{
"id":"1", "name":"Joe Doe", "books":["1","3"]
}'
curl -XPUT 'localhost:9200/clients/client/2' -d '{
"id":"2", "name":"Jane Doe", "books":["3"]
}'
curl -XPUT 'localhost:9200/books/book/1' -d '{
"id":"1", "title":"Test book one"
}'
curl -XPUT 'localhost:9200/books/book/2' -d '{
"id":"2", "title":"Test book two"
}'
curl -XPUT 'localhost:9200/books/book/3' -d '{
"id":"3", "title":"Test book three"
}'

接下来想象需求如下,我们希望展示某个用户购买的所有书籍,以id为1的user为例。当然,我们可以先执行一个请求 curl -XGET 'localhost:9200/clients/client/1'得到当前顾客的购买记录,然后把books域中的值取出来,执行第二个查询:

curl -XGET 'localhost:9200/books/_search' -d '{
"query" : {
"ids" : {
"type" : "book",
"values" : [ "1", "3" ]
}
}
}'

这样做太麻烦了,ElasticSearch 0.90版本新引入了 关键词查询过滤器(term lookup filter),该过滤器只需要一个查询就可以将上面两个查询才能完成的事情搞定。使用该过滤器的查询如下:

curl -XGET 'localhost:9200/books/_search' -d '{
"query" : {
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"terms" : {
"id" : {
"index" : "clients",
"type" : "client",
"id" : "1",
"path" : "books"
},
"_cache_key" : "terms_lookup_client_1_books"
}
}
}
}
}'

请注意_cache_key参数的值,可以看到其值为terms_lookup_client_1_books,它里面包含了顾客id信息。请注意,如果给不同的查询设置了相同的_cache_key,那么结果就会出现不可预知的错误。这是因为ElasticSearch会基于指定的key来存储查询结果,然后在不同的查询中复用。 接下来看看上述查询的返回值:

{
...
"hits" : {
"total" : 2,
"max_score" : 1.0,
"hits" : [ {
"_index" : "books",
"_type" : "book",
"_id" : "1",
"_score" : 1.0, "_source" : {"id":"1", "title":"Test book one"}
}, {
"_index" : "books",
"_type" : "book",
"_id" : "3",
"_score" : 1.0, "_source" : {"id":"3", "title":"Test book three"}
} ]
}
}

这正是我们希望看到的结果,太棒了!

term filter的工作原理

回顾我们发送到ElasticSearch的查询命令。可以看到,它只是一个简单的过滤查询,包含一个全量查询和一个terms 过滤器。只是该查询命令中,terms 过滤器使用了一种不同的技巧——不是明确指定某些term的值,而是从其它的索引中动态加载。

可以看到,我们的过滤器基于id域,这是因为只需要id域就整合其它所有的属性。接下来就需要关注id域中的新属性:index,type,id,和path。idex属性指明了加载terms的索引源(在本例中是clients索引)。type属性告诉ElasticSearch我们的目标文档类型(在本例中是client类型)。id属性指明的我们在指定索引的指文档类型中的目标文档。最后,path属性告诉ElasticSearch应该从哪个域中加载term,在本例中是clients索引的books域。 总结一下,ElasticSearch所做的工作就是从clients索引的client文档类型中,id为1的文档里加载books域中的term。这些取得的值将用于terms filter来过滤从books索引(命令执行的目的地是books索引)中查询到的文档,过滤条件是文档id域(本例中terms filter名称为id)的值在过滤器中存在。

elasticsearch filters特性的更多相关文章

  1. ElasticStack学习(七):ElasticSearch之Mapping初探

    一.Mapping的概念 1.Mapping类似于数据库中的Schema的定义,作用如下: 1)定义索引中的字段的名称: 2)定义字段的数据类型,例如字符串.数字.日期.布尔等: 3)对每个字段进行倒 ...

  2. 学习下ElasticSearch

    ElasticSearch基础概念 Elasticsearch的Head插件安装 Elasticsearch在Centos 7上的安装常见的问题 使用场景:比如分库的情况下,你想统计所有数据的报表,就 ...

  3. 《读书报告 -- Elasticsearch入门 》-- 安装以及简单使用(1)

    <读书报告 – Elasticsearch入门 > 第一章 Elasticsearch入门 Elasticsearch是一个实时的分布式搜索和分析引擎,使得人们可以在一定规模上和一定速度上 ...

  4. Elasticsearch 文档专用

    ES安装等操作 http://blog.csdn.net/cnweike/article/details/33736429 https://www.elastic.co/guide/cn/elasti ...

  5. ELK之Elasticsearch

    安装并运行Elasetisearch cd elasticsearch-<version> ./bin/elasticsearch 如果你想把 Elasticsearch 作为一个守护进程 ...

  6. 可以执行全文搜索的原因 Elasticsearch full-text search Kibana RESTful API with JSON over HTTP elasticsearch_action es 模糊查询

    https://www.elastic.co/guide/en/elasticsearch/guide/current/getting-started.html Elasticsearch is a ...

  7. 如何在python中使用Elasticsearch

    什么是 Elasticsearch ​ 想查数据就免不了搜索,搜索就离不开搜索引擎,百度.谷歌都是一个非常庞大复杂的搜索引擎,他们几乎索引了互联网上开放的所有网页和数据.然而对于我们自己的业务数据来说 ...

  8. ef core SoftDelete Multi-tenancy 软删除、多租户实现 Global Query Filters

    ef core提供了Global Query Filters特性来实现多租户与软删除,收集了一些实现方法. 最简单的例子时微软官方的特性解释. https://docs.microsoft.com/e ...

  9. PB级数据实时查询,滴滴Elasticsearch多集群架构实践

    PB级数据实时查询,滴滴Elasticsearch多集群架构实践  mp.weixin.qq.com 点击上方"IT牧场",选择"设为星标"技术干货每日送达 点 ...

随机推荐

  1. SpringBoot Redis 订阅发布

    一  配置application.yml spring: redis: jedis: pool: max-active: 10 min-idle: 5 max-idle: 10 max-wait: 2 ...

  2. anaconda新建环境

    安装tensorflow等如下: https://blog.csdn.net/Gransand/article/details/80713810 修改默认打开目录如下: https://blog.cs ...

  3. Java 几种队列区别的简单说明

    前言 队列,字面意思就可以明白. 是一种线性的数据暂存与管理工具. 也可以让各种业务功能进行逐个的队列运行. 此篇博客只说明一下Java有几种队列 未阻塞和阻塞队列的区别 未阻塞: 1.未阻塞的队列在 ...

  4. Chrome DNS_PROBE_FINISHED_NXDOMAIN

    win10 下的Chrome访问网站时,提示DNS_PROBE_FINISHED_NXDOMAIN 解决方法很简单: 用管理员身份打开cmd后,运行如下指令即可解决问题. 运行命令: netsh wi ...

  5. 列表中的index,extend,count方法

    列表中的index,extend,count方法 #_author:Administrator#date:2019/10/24#1.index方法l=['blue','red','white','bl ...

  6. Kunbernetes从私有仓库nexus拉取镜像

    1.docker登陆认证 [root@master ~]# vim /etc/docker/daemon.json { "insecure-registries": [" ...

  7. Socket.EndReceive 方法 (IAsyncResult)

        .NET Framework (current version)   其他版本 .NET Framework 4 .NET Framework 3.5 .NET Framework 3.0 . ...

  8. 杂项-语言-Swift:Swift

    ylbtech-杂项-语言-Swift:Swift Swift,苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C*共同运行于Mac OS和iOS平台,用于搭建基于 ...

  9. 让delphi2010能有delphi7的版面布局

    如何让delphi2010能有delphi7的版面布局呢?答案是肯定的,方法如下: 1)在工具栏点右键,选择components 会看到delphi7中的组件显示面板 2)tools > opt ...

  10. LightOJ-1138-Trailing Zeroes (III)-二分+求N!末尾0

    You task is to find minimal natural number N, so that N! contains exactly Q zeroes on the trail in d ...