2023-08-08  阅读(353)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/146

本章,我将通过示例讲解下Elasticsearch中Term Filter的使用。所谓Term Filter,就是不会对搜索关键词进行分词操作,根据exact value进行搜索,数字、boolean、date天然支持这种方式。但是对于text类型的字段需要特别注意,ES默认会对text字段先分词,再检索。

一、案例实战

1.1 数据准备

先批量插入一些论坛帖子信息,索引为forum

    POST /forum/_bulk
    { "index": { "_id": 1 }}
    { "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }
    { "index": { "_id": 2 }}
    { "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }
    { "index": { "_id": 3 }}
    { "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }
    { "index": { "_id": 4 }}
    { "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }

mapping结构

我们看下这个索引的mapping:

    #请求:
    GET /forum/_mapping
    
    #响应:
    {
      "forum" : {
        "mappings" : {
          "properties" : {
            "articleID" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "hidden" : {
              "type" : "boolean"
            },
            "postDate" : {
              "type" : "date"
            },
            "userID" : {
              "type" : "long"
            }
          }
        }
      }
    }

可以看到postDate默认就是date类型。这里关键说下articleID,它的类型是text,Elasticsearch默认会对text类型的字段进行分词,建立倒排索引;其次,还会生成一个keyword字段,这个keyword就是articleID的内容,不会分词,用于建立正排索引,上述256的意思是:如果内容过长,只保留256个字符。

我们通过_analyze命令来理解下,先看下默认的articleID字段:

    #请求:分析下aticleID的默认分词结果
    GET /forum/_analyze
    {
      "field": "articleID",
      "text":"XHDK-A-1293-#fJ3" 
    }
    
    #响应:可以看到"XHDK-A-1293-#fJ3"其实被normalization了
    {
      "tokens" : [
        {
          "token" : "xhdk",
          "start_offset" : 0,
          "end_offset" : 4,
          "type" : "<ALPHANUM>",
          "position" : 0
        },
        {
          "token" : "a",
          "start_offset" : 5,
          "end_offset" : 6,
          "type" : "<ALPHANUM>",
          "position" : 1
        },
        {
          "token" : "1293",
          "start_offset" : 7,
          "end_offset" : 11,
          "type" : "<NUM>",
          "position" : 2
        },
        {
          "token" : "fj3",
          "start_offset" : 13,
          "end_offset" : 16,
          "type" : "<ALPHANUM>",
          "position" : 3
        }
      ]
    }

再来看下articleID.keyword字段:

    #请求:分析下articleID.keyword的默认分词结果
    GET /forum/_analyze
    {
      "field": "articleID.keyword",
      "text":"XHDK-A-1293-#fJ3"
    
    }
    #响应:可以看到"XHDK-A-1293-#fJ3"压根没被分词,原样返回了
    {
      "tokens" : [
        {
          "token" : "XHDK-A-1293-#fJ3",
          "start_offset" : 0,
          "end_offset" : 16,
          "type" : "word",
          "position" : 0
        }
      ]
    }

1.2 term filter示例

接着,我们来看下如何使用Term Filter。首先看一个示例:根据帖子ID搜索帖子。由于用了term filter语法,所以不会对搜索关键字进行分词:

    #请求:我们不关心相关度分数,所以用了constant_score,将相关度分数置为1
    GET /forum/_search
    {
        "query" : {
            "constant_score" : { 
                "filter" : {
                    "term" : { 
                        "articleID" : "XHDK-A-1293-#fJ3"
                    }
                }
            }
        }
    }
    #响应:因为搜索关键字"XHDK-A-1293-#fJ3"不分词,而articleID这个字段本身是分词的,所以查不出结果
    {
      "took" : 5,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 0,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      }
    }

我们直接对articleID进行term filter是查不出结果的,需要使用articleID.keyword:

    #请求:对articleID.keyword进行term filter
    GET /forum/_search
    {
        "query" : {
            "constant_score" : { 
                "filter" : {
                    "term" : { 
                        "articleID.keyword" : "XHDK-A-1293-#fJ3"
                    }
                }
            }
        }
    }
    #响应:因为articleID.keyword保存在完整text内容,所以可以匹配到
    {
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "forum",
            "_type" : "article",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "articleID" : "XHDK-A-1293-#fJ3",
              "userID" : 1,
              "hidden" : false,
              "postDate" : "2017-01-01"
            }
          }
        ]
      }
    }

二、Filter底层原理

2.1 匹配document

term filter在执行时,首先会在倒排索引中匹配filter条件,也就是我们的搜索关键字,用于获取document list。我们以postDatge来举个例子,如果查找“2017-02-02”,会发现”2017-02-02“对应的document list是doc2、doc3:

word doc1 doc2 doc3
2017-01-01 Y Y N
2017-02-02 N Y Y
2017-03-03 Y Y Y

2.2 构建bitset

接着,为每个fiter条件构建一个bitset。bitset就是一个二进制的数组,数组每个元素都是0或1,用来标识某个doc对这个filter条件是否匹配,匹配就是1,否则为0。比如,doc1不匹配"2017-02-02",而doc2和do3是匹配的,所以"2017-02-02"这个filter条件的bitset就是[0, 1, 1]。

2.3 遍历bitset

由于在一个search请求中,可以有多个filter条件,而filter条件都会对应一个bitset。所以这一步,ES会从最稀疏的bitset开始遍历,优先过滤掉尽可能多的数据。比如我们的filter条件是postDate=2017-01-01,userID=1,对应的bitset是:
postDate: [0, 0, 1, 1, 0, 0]
userID: [0, 1, 0, 1, 0, 1]

那么遍历完两个bitset之后,找到匹配所有filter条件只有doc4,就将其作为结果返回给client了。

2.4 缓存bitset

Elasticsearch会将一些频繁访问的filter条件和它对应的bitset缓存在内存中,这样就可以提高检索效率了。

注意,如果document保存在某个很小的segment上的话(segment记录数<1000,或segment大小<index总大小的3%),Elasticsearch就不会对其缓存。因为segment很小的话,会在后台被自动合并,那么缓存也没有什么意义了,因问segment很快就消失了。

这里就可以看出,filter为什么比query的性能更好了,filter除了不需要计算相关度分数并按其排序外,filter还会缓存检索结果对应的bitset。

2.5 bitset更新

如果document有新增或修改,那么filter条件对应的cached bitset会被自动更新。举个例子,假设postDate=2017-01-01对应的bitset为[0, 0, 1, 0]。

  • 如果新增一条doc5:id=5,postDate=2017-01-01,那postDate=2017-01-01这个filter的bitset会全自动更新成[0, 0, 1, 0, 1];
  • 如果修改doc1:id=1,postDate=2016-12-30,那postDate=2016-01-01这个filter的bitset会全自动更新成[1, 0, 1, 0, 1];

三、总结

本章,我介绍了term filter的基本使用和底层的bitset原理。关于term filter的更多API,读者可以参考官方文档,本系统的目的是介绍Elasticsearch中各种检索功能的基本使用,重点关注的是底层的实现原理。

阅读全文
  • 点赞