• Elasticsearch深入6


    深度探秘搜索技术_filter执行原理深度剖析

     (1)在倒排索引中查找搜索串,获取document list

     date来举例

     word                 doc1          doc2          doc3

     2017-01-01       *                *

    2017-02-02                         *                *

    2017-03-03       *                *                *

     filter:2017-02-02

     到倒排索引中一找,发现2017-02-02对应的document list是doc2,doc3

     (2)为每个在倒排索引中搜索到的结果,构建一个bitset,[0, 0, 0, 1, 0, 1]

     非常重要

     使用找到的doc list,构建一个bitset,就是一个二进制的数组,数组每个元素都是0或1,用来标识一个doc对一个filter条件是否匹配,如果匹配就是1,不匹配就是0

     [0, 1, 1]

     doc1:不匹配这个filter的

    doc2和do3:是匹配这个filter的

     尽可能用简单的数据结构去实现复杂的功能,可以节省内存空间,提升性能

     (3)遍历每个过滤条件对应的bitset,优先从最稀疏的开始搜索,查找满足所有条件的document

     后面会讲解,一次性其实可以在一个search请求中,发出多个filter条件,每个filter条件都会对应一个bitset

    遍历每个filter条件对应的bitset,先从最稀疏的开始遍历

     [0, 0, 0, 1, 0, 0]:比较稀疏

    [0, 1, 0, 1, 0, 1]

     先遍历比较稀疏的bitset,就可以先过滤掉尽可能多的数据

     遍历所有的bitset,找到匹配所有filter条件的doc

    请求:filter,postDate=2017-01-01,userID=1

     postDate: [0, 0, 1, 1, 0, 0]

    userID:   [0, 1, 0, 1, 0, 1]

     遍历完两个bitset之后,找到的匹配所有条件的doc,就是doc4

     就可以将document作为结果返回给client了

     (4)caching bitset,跟踪query,在最近256个query中超过一定次数的过滤条件,缓存其bitset。对于小segment(<1000,或<3%),不缓存bitset。

     比如postDate=2017-01-01,[0, 0, 1, 1, 0, 0],可以缓存在内存中,这样下次如果再有这个条件过来的时候,就不用重新扫描倒排索引,反复生成bitset,可以大幅度提升性能。

     在最近的256个filter中,有某个filter超过了一定的次数,次数不固定,就会自动缓存这个filter对应的bitset

     segment(上半季),filter针对小segment获取到的结果,可以不缓存,segment记录数<1000,或者segment大小<index总大小的3%

     segment数据量很小,此时哪怕是扫描也很快;segment会在后台自动合并,小segment很快就会跟其他小segment合并成大segment,此时就缓存也没有什么意义,segment很快就消失了

     针对一个小segment的bitset,[0, 0, 1, 0]

     filter比query的好处就在于会caching,但是之前不知道caching的是什么东西,实际上并不是一个filter返回的完整的doc list数据结果。而是filter bitset缓存起来。下次不用扫描倒排索引了。

     (5)filter大部分情况下来说,在query之前执行,先尽量过滤掉尽可能多的数据

     query:是会计算doc对搜索条件的relevance score,还会根据这个score去排序

    filter:只是简单过滤出想要的数据,不计算relevance score,也不排序

     (6)如果document有新增或修改,那么cached bitset会被自动更新

     postDate=2017-01-01,[0, 0, 1, 0]

    document,id=5,postDate=2017-01-01,会自动更新到postDate=2017-01-01这个filter的bitset中,全自动,缓存会自动更新。postDate=2017-01-01的bitset,[0, 0, 1, 0, 1]

    document,id=1,postDate=2016-12-30,修改为postDate-2017-01-01,此时也会自动更新bitset,[1, 0, 1, 0, 1]

     

    (7)以后只要是有相同的filter条件的,会直接来使用这个过滤条件对应的cached bitset

     手动控制全文检索结果的精准度

    1、为帖子数据增加标题字段

    POST /forum/article/_bulk
    { "update": { "_id": "1"} }
    { "doc" : {"title" : "this is java and elasticsearch blog"} }
    { "update": { "_id": "2"} }
    { "doc" : {"title" : "this is java blog"} }
    { "update": { "_id": "3"} }
    { "doc" : {"title" : "this is elasticsearch blog"} }
    { "update": { "_id": "4"} }
    { "doc" : {"title" : "this is java, elasticsearch, hadoop blog"} }
    { "update": { "_id": "5"} }
    { "doc" : {"title" : "this is spark blog"} }

    2、搜索标题中包含java或elasticsearch的blog

    这个,就跟之前的那个term query,不一样了。不是搜索exact value,是进行full text全文检索。
    match query,是负责进行全文检索的。当然,如果要检索的field,是not_analyzed类型的,那么match query也相当于term query

    GET /forum/article/_search
    {
        "query":{
            "match":{
                "title":"java elasticsearch"
            }
        }
    }

    使用诸如上面的match query进行多值搜索的时候,es会在底层自动将这个match query转换为bool的语法
    bool should,指定多个搜索词,同时使用term query

    {
        "bool":{
            "should":[
                {
                    "term":{
                        "title":"java"
                    }
                },
                {
                    "term":{
                        "title":"elasticsearch"
                    }
                }
            ]
        }
    }

    3、搜索标题中包含java和elasticsearch的blog

    搜索结果精准控制的第一步:灵活使用and关键字,如果你是希望所有的搜索关键字都要匹配的,那么就用and,可以实现单纯match query无法实现的效果想到了query_string中的and有啥区别吧

    GET /forum/article/_search
    {
        "query":{
            "match":{
                "title":{
                    "query":"java elasticsearch",
                    "operator":"and"
                }
            }
        }
    }

    底层转换

    {
        "bool":{
            "must":[
                {
                    "term":{
                        "title":"java"
                    }
                },
                {
                    "term":{
                        "title":"elasticsearch"
                    }
                }
            ]
        }
    }

    4、搜索包含java,elasticsearch,spark,hadoop,4个关键字中,至少3个的blog

    控制搜索结果的精准度的第二步:指定一些关键字中,必须至少匹配其中的多少个关键字,才能作为结果返回

    GET /forum/article/_search
    {
        "query":{
            "match":{
                "title":{
                    "query":"java elasticsearch spark hadoop",
                    "minimum_should_match":"75%"
                }
            }
        }
    }

    5、用bool组合多个搜索条件,来搜索title

    GET /forum/article/_search
    {
        "query":{
            "bool":{
                "must":{
                    "match":{
                        "title":"java"
                    }
                },
                "must_not":{
                    "match":{
                        "title":"spark"
                    }
                },
                "should":[
                    {
                        "match":{
                            "title":"hadoop"
                        }
                    },
                    {
                        "match":{
                            "title":"elasticsearch"
                        }
                    }
                ]
            }
        }
    }

    6、bool组合多个搜索条件,如何计算relevance score

    must和should搜索对应的分数,加起来,除以must和should的总数

    排名第一:java,同时包含should中所有的关键字,hadoop,elasticsearch
    排名第二:java,同时包含should中的elasticsearch
    排名第三:java,不包含should中的任何关键字

    should是可以影响相关度分数的

    must是确保说,谁必须有这个关键字,同时会根据这个must的条件去计算出document对这个搜索条件的relevance score
    在满足must的基础之上,should中的条件,不匹配也可以,但是如果匹配的更多,那么document的relevance score就会更高

    {
        "took":6,
        "timed_out":false,
        "_shards":{
            "total":5,
            "successful":5,
            "failed":0
        },
        "hits":{
            "total":3,
            "max_score":1.3375794,
            "hits":[
                {
                    "_index":"forum",
                    "_type":"article",
                    "_id":"4",
                    "_score":1.3375794,
                    "_source":{
                        "articleID":"QQPX-R-3956-#aD8",
                        "userID":2,
                        "hidden":true,
                        "postDate":"2017-01-02",
                        "tag":[
                            "java",
                            "elasticsearch"
                        ],
                        "tag_cnt":2,
                        "view_cnt":80,
                        "title":"this is java, elasticsearch, hadoop blog"
                    }
                },
                {
                    "_index":"forum",
                    "_type":"article",
                    "_id":"1",
                    "_score":0.53484553,
                    "_source":{
                        "articleID":"XHDK-A-1293-#fJ3",
                        "userID":1,
                        "hidden":false,
                        "postDate":"2017-01-01",
                        "tag":[
                            "java",
                            "hadoop"
                        ],
                        "tag_cnt":2,
                        "view_cnt":30,
                        "title":"this is java and elasticsearch blog"
                    }
                },
                {
                    "_index":"forum",
                    "_type":"article",
                    "_id":"2",
                    "_score":0.19856805,
                    "_source":{
                        "articleID":"KDKE-B-9947-#kL5",
                        "userID":1,
                        "hidden":false,
                        "postDate":"2017-01-02",
                        "tag":[
                            "java"
                        ],
                        "tag_cnt":1,
                        "view_cnt":50,
                        "title":"this is java blog"
                    }
                }
            ]
        }
    }

    7、搜索java,hadoop,spark,elasticsearch,至少包含其中3个关键字

    默认情况下,should是可以不匹配任何一个的,比如上面的搜索中,this is java blog,就不匹配任何一个should条件
    但是有个例外的情况,如果没有must的话,那么should中必须至少匹配一个才可以
    比如下面的搜索,should中有4个条件,默认情况下,只要满足其中一个条件,就可以匹配作为结果返回

    但是可以精准控制,should的4个条件中,至少匹配几个才能作为结果返回

    GET /forum/article/_search
    {
        "query":{
            "bool":{
                "should":[
                    {
                        "match":{
                            "title":"java"
                        }
                    },
                    {
                        "match":{
                            "title":"elasticsearch"
                        }
                    },
                    {
                        "match":{
                            "title":"hadoop"
                        }
                    },
                    {
                        "match":{
                            "title":"spark"
                        }
                    }
                ],
                "minimum_should_match":3
            }
        }
    }

    boost 

    搜索条件的权重,boost,可以将某个搜索条件的权重加大,此时当匹配这个搜索条件和匹配另一个搜索条件的document,计算relevance score时,匹配权重更大的搜索条件的document,relevance score会更高,当然也就会优先被返回回来

    默认情况下,搜索条件的权重都是一样的,都是1

    doc1 java jdk7 

    doc2 java jdk8

    doc3 java jdk9

    doc4 java jdk9

    需求搜索 必须包含java的doc但是should中有三个条件 jdk7 jdk8 jdk9 所以doc 1,2,3都会被返回但是分数不一致,但是现在我就想对jdk8更加上心想让jdk8分数最高排在最前使用boost

    {
        "match":{
            "title":{
                "query":"jdk8",
                "boost":5
            }
        }
    }

     

    案例实战基于dis_max实现best fields策略进行多字段搜索

     

    1、为帖子数据增加content字段

     

    POST /forum/article/_bulk

    { "update": { "_id": "1"} }

    { "doc" : {"content" : "i like to write best elasticsearch article"} }

    { "update": { "_id": "2"} }

    { "doc" : {"content" : "i think java is the best programming language"} }

    { "update": { "_id": "3"} }

    { "doc" : {"content" : "i am only an elasticsearch beginner"} }

    { "update": { "_id": "4"} }

    { "doc" : {"content" : "elasticsearch and hadoop are all very good solution, i am a beginner"} }

    { "update": { "_id": "5"} }

    { "doc" : {"content" : "spark is best big data solution based on scala ,an programming language similar to java"} }

     2、搜索title或content中包含java或solution的帖子

     下面这个就是multi-field搜索,多字段搜索

     GET /forum/article/_search

    {

        "query": {

            "bool": {

                "should": [

                    { "match": { "title": "java solution" }},

                    { "match": { "content":  "java solution" }}

                ]

            }

        }

    }

     

    3、结果分析

     期望的是doc5,结果是doc2,doc4排在了前面

     计算每个document的relevance score:每个query的分数,乘以matched query数量(匹配的query),除以总query数量

     算一下doc4的分数

     { "match": { "title": "java solution" }},针对doc4,是有一个分数的

    { "match": { "content":  "java solution" }},针对doc4,也是有一个分数的

     所以是两个分数加起来,比如说,1.1 + 1.2 = 2.3

    matched query数量 = 2

    总query数量 = 2

     2.3 * 2 / 2 = 2.3

     算一下doc5的分数

     { "match": { "title": "java solution" }},针对doc5,是没有分数的

    { "match": { "content":  "java solution" }},针对doc5,是有一个分数的

     所以说,只有一个query是有分数的,比如2.3

    matched query数量 = 1

    总query数量 = 2

     2.3 * 1 / 2 = 1.15

     doc5的分数 = 1.15 < doc4的分数 = 2.3

     4、best fields策略,dis_max

     best fields策略,就是说,搜索到的结果,应该是某一个field中匹配到了尽可能多的关键词,被排在前面;而不是尽可能多的field匹配到了少数的关键词,排在了前面  一句话概括就是 doc1如果一个filed的能够匹配多个关键字比多个filed匹配多个关键字权重要高>_<

     dis_max语法,直接取多个query中,分数最高的那一个query的分数即可

     { "match": { "title": "java solution" }},针对doc4,是有一个分数的,1.1

    { "match": { "content":  "java solution" }},针对doc4,也是有一个分数的,1.2

    取最大分数,1.2

     { "match": { "title": "java solution" }},针对doc5,是没有分数的

    { "match": { "content":  "java solution" }},针对doc5,是有一个分数的,2.3

    取最大分数,2.3

     然后doc4的分数 = 1.2 < doc5的分数 = 2.3,所以doc5就可以排在更前面的地方,符合我们的需要

     GET /forum/article/_search

    {

        "query": {

            "dis_max": {

                "queries": [

                    { "match": { "title": "java solution" }},

                    { "match": { "content":  "java solution" }}

                ]

            }

        }

    }

     id为5的doc就会排在前面去了

     可能在实际场景中出现的一个情况是这样的:

    (1)某个帖子,doc1,title中包含java,content不包含java beginner任何一个关键词
    (2)某个帖子,doc2,content中包含beginner,title中不包含任何一个关键词
    (3)某个帖子,doc3,title中包含java,content中包含beginner
    (4)最终搜索,可能出来的结果是,doc1和doc2排在doc3的前面,而不是我们期望的doc3排在最前面

    dis_max,只是取分数最高的那个query的分数而已。

    2、dis_max只取某一个query最大的分数,完全不考虑其他query的分数

    3、使用tie_breaker将其他query的分数也考虑进去

    tie_breaker参数的意义,在于说,将其他query的分数,乘以tie_breaker,然后综合与最高分数的那个query的分数,综合在一起进行计算
    除了取最高分以外,还会考虑其他的query的分数
    tie_breaker的值,在0~1之间,是个小数,就ok

    GET /forum/article/_search
    {
        "query":{
            "dis_max":{
                "queries":[
                    {
                        "match":{
                            "title":"java beginner"
                        }
                    },
                    {
                        "match":{
                            "body":"java beginner"
                        }
                    }
                ],
                "tie_breaker":0.3
            }
        }
    }

     

     

  • 相关阅读:
    SpringBoot | Thymeleaf | 局部更新
    面试 | 冒泡排序优化
    JSP && Servlet | AXIS 0配置 入门
    155. 最小栈
    idea | 命名空间改过后重新导入项目方法
    Java | 基础归纳 | Map.Entry<String, String>
    08_Azkaban案例实践1_Command单一job示例
    07_Azkaban工作流调度器简介及其安装
    06_工作流调度器概述
    05_ Flume多级Agent之间串联案例
  • 原文地址:https://www.cnblogs.com/jiahaoJAVA/p/11028720.html
Copyright © 2020-2023  润新知