作者
刘晓国,Elastic 公司社区布道师。新加坡国立大学硕士,西北工业大学硕士,曾就职于新加坡科技,康柏电脑,通用汽车,爱立信,诺基亚,Linaro,Ubuntu,Vantiq 等企业。
编辑
丛聿,架构师(搜索方向)
前言
Elasticsearch 是一款功能强大且功能丰富的搜索工具。本文将介绍一种小众的数据类型 Percolator ,同时介绍Percolate query的使用。 您需要基本了解 Elasticsearch,尤其是mapping和search。
概念
Elasticsearch 的正常工作流程是将文档(JSON数据)存储在索引中,然后在执行搜索时通过索引查询这些文档的信息。设想如果反转这种使用流程将如何(即先有查询条件,再有文档),Percolate即可实现这种逆转的流程。其使用流程是先存储search条件,之后使用文档询问是否可命中这些搜索条件。本文接下来将介绍如何构造和使用percolator。
Percolation功能围绕percolator字段类型展开。 与其他字段类型一样(先在mappings中定义,再进行写入),不同的是它将搜索条件作为文档进行存储。当存储数据时,索引会将此搜索条件的文档处理为可执行形式,并将其保存以备后用。
Percolate query接受一个或多个文档,并返回预先存储的搜索条件文档(该条件至少匹配一个传入的文档)。在执行搜索时,Percolate query的工作原理与其他任何查询模式一样,不同的一些细节将在下文介绍。
深入理解
在底层,具有percolate字段的索引将保留于一个隐藏的索引(内存中)。查询时,首先将在 percolate query 中列出的文档放入该索引,然后对该索引执行常规查询,看与原始的含 percolate 字段的搜索条件是否匹配。
该隐藏索引是从原始 percolator 索引获取其映射的。因此,用于 percolate query 的索引字段需要具有适合原始数据和查询文档数据的mappings配置。
这引入了一些索引管理的问题,因为你的索引数据和 percolate query 文档可能以不同的方式使用同一字段。一个简单的方式是使用对象类型(object type) 将 percolate 相关的映射与普通文档映射分开,具体可参考后文给出的例子。
假设你使用的查询最初是为另一个索引A中的数据编写的,那么最直接的方法是将数据隔离以避免数据直接写到 percolate 索引中去,并将索引A中根级别的mappings在 percolate索引中进行定义。
此外,由于percolate field被解析为搜索条件并在索引时保存,因此在升级ES主版本后可能需要reindex Percolate文档。
示例
在此示例中,我们将建立一个索引,该索引含有保存的玩具名字和玩具价格搜索条件。其背后思路是,用户输入搜索词和最高价格,然后在与该玩具名匹配的商品价格低于用户指定价格时立即得到通知。此外用户还可以打开和关闭这些通知。下面的映射通过percolate索引来支持此功能。与保存的搜索条件本身相关的字段位于search对象中,而与原始玩具相关的字段位于映射的根级别。
首先,我们使用如下命令来创建一个索引:
PUT toys {
"mappings": {
"properties": {
"search": {
"properties": {
"query": {
"type": "percolator"
},
"user_id": {
"type": "integer"
},
"enabled": {
"type": "boolean"
}
}
},
"price": {
"type": "float"
},
"description": {
"type": "text"
}
}
}
}
我们接着使用命令写入一个文档,即用户的查询条件。此处我们将数据存储在 search 对象字段中。price 和 description 的映射仅用于支持 percolate query。
PUT toys/_doc/1
{
"search": {
"user_id": 5,
"enabled": true,
"query": {
"bool": {
"filter": [
{
"match": {
"description": {
"query": "nintendo switch"
}
}
},
{
"range": {
"price": {
"lte": 300
}
}
}
]
}
}
}
}
查询时,我们要同时使用普通对象字段和“特殊的” percolator 字段。 此查询将在用户搜索时检查是否有当前启用的搜索条件与文档匹配。
GET toys/_search
{
"query": {
"bool": {
"filter": [
{
"percolate": {
"field": "search.query",
"document": {
"description": "Nintendo Switch",
"price": 250
}
}
},
{
"term": {
"search.enabled": true
}
},
{
"term": {
"search.user_id": 5
}
}
]
}
}
}
请注意,此处结合使用了基础字段的查询(search.user_id和search.enabled字段),以及percolator条件字段的查询(search.query),用以对指定的用户ID在启用状态下生效。
运行上面的指令后,我们可以看到如下结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.0,
"hits" : [
{
"_index" : "toys",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.0,
"_source" : {
"search" : {
"user_id" : 5,
"enabled" : true,
"query" : {
"bool" : {
"filter" : [
{
"match" : {
"description" : {
"query" : "nintendo switch"
}
}
},
{
"range" : {
"price" : {
"lte" : 300
}
}
}
]
}
}
}
},
"fields" : {
"_percolator_document_slot" : [
0
]
}
}
]
}
}
如果我们改用如下搜索条件
GET toys/_search
{
"query": {
"bool": {
"filter": [
{
"percolate": {
"field": "search.query",
"document": {
"description": "Nintendo Switch",
"price": 500
}
}
},
{
"term": {
"search.enabled": true
}
},
{
"term": {
"search.user_id": 5
}
}
]
}
}
}
其中price不满足percolate预先存储的条件,因此将找不到任何结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
结束语
在实际使用中,我们可以在 Logstash的Elasticsearch过滤器中 针对每个事件来使用 Elasticsearch 做 query。即我们也可以得到这个事件是否满足预设的 search条件,如果满足条件则可以执行其后续流程。
参考
【1】https://www.elastic.co/blog/elasticsearch-data-enrichment-with-logstash-a-few-security-examples
声明
本文由作者刘晓国授权转载,版权归作者所有,未经许可不得擅自转载或引用。
【阿里云Elastic Stack】100%兼容开源ES,独有9大能力,提供免费 X-pack服务(单节点价值$6000)
相关活动
更多折扣活动,请访问阿里云 Elasticsearch 官网
阿里云 Elasticsearch 商业通用版,1核2G ,SSD 20G首月免费
阿里云 Logstash 2核4G首月免费
下载白皮书:Elasticsearch 八大经典场景应用