本章,我将通过示例讲解下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中各种检索功能的基本使用,重点关注的是底层的实现原理。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。