1. 准备测试数据
POST /article/_doc/_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"}
GET /article/_mapping/_doc
{
"article" : {
"mappings" : {
"_doc" : {
"properties" : {
"articleId" : {
"type" : "text",
"fields" : {
# 不分词,最多保留256个字符
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"hidden" : {
"type" : "boolean"
},
"postDate" : {
"type" : "date"
},
"userId" : {
"type" : "long"
}
}
}
}
}
}
说明:测试数据会随着练习而不断加入新数据
2. term filter
输入的搜索文本不分词,直接拿去倒排索引中进行精确匹配
# 根据用户ID搜索帖子(返回2条结果)
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"userId": 1
}
}
}
}
}
# 搜索没有隐藏的帖子(返回3条结果)
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"hidden": false
}
}
}
}
}
# 根据发帖日期搜索帖子(返回2条结果)
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"postDate": "2017-01-01"
}
}
}
}
}
# 根据帖子ID搜索帖子(无结果)
# text类型的field,建立倒排索引的时候,就会进行分词
# 分词以后,原本的articleID就没有了,只有分词后的各个单词存在于倒排索引中(qqpx、r、3956、ad8)
# term,是不对搜索文本分词的,所有精确匹配是匹配不到的
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"articleId": "QQPX-R-3956-#aD8"
}
}
}
}
}
# 根据帖子ID搜索帖子(1条结果)
# articleID.keyword,是es内置建立的field,就是不分词的。
# 所以一个articleID过来的时候,会建立两次索引
# 一次是要分词的,分词后放入倒排索引
# 另外一次是基于articleID.keyword,不分词,最多保留256个字符最多,直接将字符串本身放入倒排索引中
# 所以term filter,对text过滤,可以考虑使用内置的field.keyword来进行匹配
# 但是有个问题,默认就保留256个字符,所以尽可能还是自己去手动建立索引,将type设置为keyword
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"articleId.keyword": "QQPX-R-3956-#aD8"
}
}
}
}
}
# 重建索引
DELETE article
# 手动指定articleId的类型,这样直接将type设置为keyword,是没有保留字符串的长度限制的
PUT article
{
"mappings": {
"_doc": {
"properties": {
"articleId": {
"type": "keyword"
}
}
}
}
}
# 重新插入数据
POST /article/_doc/_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"}
# 然后直接使用articleId来进行term filter是有返回结果的
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"articleId": "QQPX-R-3956-#aD8"
}
}
}
}
}
知识点总结:
- term filter:对输入的内容进行精确匹配,数字、boolean、date天然支持精确匹配,而text类型的字段则要设置为keyword类型才可以使用term filter
3. filter执行原理剖析
查询条件:假设查询"2017-02-02"这个日期,{filter: {term: "2017-02-02"}}
且倒排索引中的数据如下,*代表存在于该文档中:
word | doc1 | doc2 | doc3 |
---|---|---|---|
2017-01-01 | * | * | |
2017-02-02 | * | * | |
2017-03-03 | * | * | * |
-
到倒排索引中查询,发现"2017-02-02"对应的document list是doc2,doc3
-
ES为每个在倒排索引中搜索到的结果,构建一个bitset,使用找到的doc list,构建一个bitset,就是一个二进制的数组,数组每个元素都是0或1,用来标识一个doc对一个filter条件是否匹配,如果匹配就是1,不匹配就是0,例如为上述filter创建的bitset为[0, 1, 1],就是代表,doc1不满足filter条件,而doc2和doc3满足filter条件,使用bitset这种简单的数据结构去实现复杂的功能,可以节省内存空间,提升性能
-
假设一次查询中有多个filter条件,遍历每个filter条件对应的bitset,优先从最稀疏的开始搜索,查找满足所有条件的document,优先从最稀疏的开始遍历,例如[0, 0, 0, 1, 0, 0]就比[1, 0, 1, 0, 0]稀疏,先遍历比较稀疏的bitset,就可以先过滤掉尽可能多的数据,遍历所有的bitset,找到匹配所有filter条件的doc
假设有两个filter条件,postDate=2017-01-01,userID=1,每个filter的bitset如下:
postDate=2017-01-01:[0, 0, 1, 1, 0, 0]
userID=1:[0, 1, 0, 1, 0, 1]
可以看到,同事满足这两个条件的doc为doc4(下标为3),于是返回doc4给客户端
-
bitset的缓存:在最近256个query中超过一定次数的过滤条件,就会缓存其bitset,对于小segment则不缓存bitset
比如postDate=2017-01-01,bitset为[0, 0, 1, 1, 0, 0],缓存在内存中,这样下次如果再有这个条件的filter的时候,就不用重新扫描倒排索引,而是直接从缓存中获取满足条件的doc list,这样可以大幅度提升性能,在最近的256个filter中,有某个filter被查询超过了一定的次数(次数不固定),就会自动缓存这个filter对应的bitset
filter针对小segment(一个index由多个segment文件组成)获取到的结果,则不会缓存,小segment是指其文档数<1000,或者它的大小<index总大小的3%,这样的segment数据量很小,哪怕是扫描其全部数据获取结果也很快,segment会在后台自动合并,小segment很快就会跟其他小segment合并成大segment,此时就缓存也没有什么意义了
filter(只是简单过滤数据,不计算评分也不排序)比query(返回结果进行评分、相关度排序)的优势就在于它的bitset的缓存,使得filter比query的性能更好
-
filter在大部分情况下会在query之前执行,这样可以先过滤掉尽可能多的数据
-
如果document有新增或修改,那么缓存的bitset会被自动更新
-
后续只要是相同的filter条件,都会直接使用这个过滤条件的缓存bitset来进行查询
4. 基于bool组合多个filter条件
# 搜索发帖日期为2017-01-01,或者帖子ID为XHDK-A-1293-#fJ3的帖子,同时要求帖子的发帖日期绝对不为2017-01-02
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should": [
{"term": {"postDate": "2017-01-01"}},
{"term": {"articleId": "XHDK-A-1293-#fJ3"}}
],
"must_not": [
{"term": {"postDate": "2017-01-02"}}
]
}
}
}
}
}
# must:所有的条件都必须匹配
# should:其中的条件匹配任意一个即可
# must_not:所有的条件都必须不匹配
# 搜索帖子ID为XHDK-A-1293-#fJ3,或者是帖子ID为JODL-X-1937-#pV7而且发帖日期为2017-01-01的帖子
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should": [
{"term": {"articleId": "XHDK-A-1293-#fJ3"}},
{
"bool": {
"must": [
{"term": {"articleId": {"value": "JODL-X-1937-#pV7"}}},
{"term": {"postDate": {"value": "2017-01-01"}}}
]
}
}
]
}
}
}
}
}
知识点总结:
- bool:must、must_not、should,组合多个过滤条件
- bool可以嵌套
- must、must_not、should之间的关系是and的关系
5. 使用terms搜索多个值
# 增加tag字段
POST /article/_doc/_bulk
{"update": {"_id": "1"}}
{"doc": {"tag": ["java", "hadoop"]}}
{"update": {"_id": "2"}}
{"doc": {"tag": ["java"]}}
{"update": {"_id": "3"}}
{"doc": {"tag": ["hadoop"]}}
{"update": {"_id": "4"}}
{"doc": {"tag": ["java", "elasticsearch"]}}
# 搜索articleID为KDKE-B-9947-#kL5或QQPX-R-3956-#aD8的帖子
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"terms": {
"articleId": ["KDKE-B-9947-#kL5", "QQPX-R-3956-#aD8"]
}
}
}
}
}
# 搜索tag中包含java的帖子
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"terms": {
"tag": ["java"]
}
}
}
}
}
# 增加一个tag_cnt字段,统计tag的个数
POST /article/_doc/_bulk
{"update": {"_id": "1"}}
{"doc": {"tag_cnt": 2}}
{"update": {"_id": "2"}}
{"doc": {"tag_cnt": 1}}
{"update": {"_id": "3"}}
{"doc": {"tag_cnt": 1}}
{"update": {"_id": "4"}}
{"doc": {"tag_cnt": 2}}
# 搜索tag中只包含java的帖子
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"must": [
{"term": {"tag_cnt": 1}},
{"terms": {"tag": ["java"]}}
]
}
}
}
}
}
知识点总结:
-
terms:多值搜索
-
terms的功能与SQL中"in"关键字类似
6. 基于range filter来进行范围过滤
# 为帖子数据增加浏览量的字段
POST /article/_doc/_bulk
{"update": {"_id": "1"} }
{"doc": {"view_cnt": 30}}
{"update": {"_id": "2"}}
{"doc": {"view_cnt": 50}}
{"update": {"_id": "3"}}
{"doc": {"view_cnt": 100}}
{"update": { "_id": "4"}}
{"doc": {"view_cnt": 80}}
# 搜索浏览量在30~60之间的帖子
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"view_cnt": {
"gt": 30,
"lt": 60
}
}
}
}
}
}
# 增加一条测试数据
PUT /article/_doc/5
{
"articleID": "DHJK-B-1395-#Ky5",
"userID": 3,
"hidden": false,
"postDate": "2017-03-01",
"tag": ["elasticsearch"],
"tag_cnt": 1,
"view_cnt": 10
}
# 搜索发帖日期在最近1个月的帖子(假设今天是2017-03-10)
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"postDate": {
"gt": "2017-03-10||-30d"
}
}
}
}
}
}
# 当天日期可以使用now来获取
GET /article/_doc/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"postDate": {
"gt": "now-30d"
}
}
}
}
}
}
知识点总结:
- range:范围过滤
- range与SQL中的"between"关键字的作用类似
网友评论