• (1.3)elasticsearch查询基础


    【1】概念性知识

    数据类型

    字符串#

    • text:用于全文索引,该类型的字段将通过分词器进行分词
    • keyword:不分词,只能搜索该字段的完整的值

    数值型#

    • long、integer、short、byte、double、float、half_float、scaled_float

    布尔#

    • boolean

    二进制#

    • binary:该类型的字段把值当做经过base64编码的字符串,默认不存储,且不可搜索

    范围类型#

    1. 范围类型表示值是一个范围,而不是一个具体的值
    2. integer_range、float_range、long_range、double_range、date_range
    3. 比如age类型是integer_range,那么值可以是{"gte":20,"lte":40};搜索"term":{"age":21}可以搜索该值

    日期-date#

      由于json类型没有date类型,所以es通过识别字符串是否符合format定义的格式来判断是否为date类型

      format默认为:strict_date_optional_time || epoch_millis

      格式

        "2022-01-01" "2022/01/01 12:10:30" 这种字符串格式

      从开始纪元(1970年1月1日0点)开始的毫秒数

    Search API 概述

    • URI Search
      • 在URL中使用查询参数
    • Requests Body Search
      • 使用ES提供的,基于JSON格式的更加完备的 query Domain Specific Language(DSL)

    指定查询的索引

    image-20210416141432505

    URI查询

    image-20210416141557248

    Request body

    image-20210416141623606

    查询返回结果解析

    image

    衡量相关性(Precision,Recall,Ranking)

    information retrieval

    • Precision(查准率):尽可能的返回较少的无关文档
    • Recall(查全率):尽量返回较多的相关文档
    • Ranking:是否能够按照相关度进行排序?

    image-20210416142906110

    URI Search 详解

    image-20210416143106395

    • q:指定查询语句,使用 Query String Syntax
    • df: 默认字段,不指定是,会对所有字段进程查询
    • Sort:排序 / from 和 size 用于分页
    • Profile 可以查看查询是如何被执行的

    (1)指定字段与泛查询

    ​ q=title:2012 / q=2012

    GET /movies/_search?q=2012&df=title  #泛查询,但默认只搜索 title字段
    GET /movies/_search?q=2012  		#泛查询,对应_all,搜索所有字段
    GET /movies/_search?q=title:2012 	#指定字段
    GET /users4/_search?q=user:"lisi"&q=age:30 # 多字段查询
    GET /movies/_search?q=title:2012
    {
    	"profile":"true"
    }
    GET /movies/_search?q=title:beautiful Mind #查找美丽心灵,在title中查询beautiful,Mind为泛查询
    

    (2)分词与词组查询 (Term、phrase)

    # 双引号括起来,就相当于PhraseQuery,整个标题这2个单词都出现过,且还要求前后顺序保持一致
    	GET /movies/_search?q=title:"Beautiful Mind"  
    # 查找美丽心灵,在title中查询beautiful,Mind为泛查询(每个字段都查)
    	GET /movies/_search?q=title:beautiful Mind 
    # 分组,Bool查询,两个term在括号中默认是 or的关系,查询 title中包含 Beautiful分词 或者 Mind 分词
    	GET /movies/_search?q=title:(Beautiful Mind)
    
    

    (3)分组()与引号""

    • title:(Beautiful Mind):表示查询 title 中的出现 beautiful Mind 或者 Mind 的 =》 Beautiful and Mind
    • title="Beautiful Mind":表示 "Beautiful Mind" 是一个整体,查询 title 中这2个词都出现过的,并且还要求前后顺序保持一致

    具体案例见上面(2)中的代码;

    (4)Bool 布尔操作

    布尔操作:

    ​ AND / OR / NOT 或者 && / || / !

    • b必须大写
    • title:(matrix NOT reloaded)
    • 默认为 OR,如:GET /movies/_search?q=title:(Beautiful Mind) #查询 title中包含 Beautiful 或者 Mind

    分组

    • +表示 must
    • -表示 must_not
    • titile:(+matrix - reloaded)

    (5)范围查询 [] 与 {} (闭区间,开区间)

    区间表示:[] 闭区间,{} 开区间

    • year:{2019 TO 2018}
    • year:[* TO 2018]

    算数符号:

    • year:>2012
    • year:(>2010 && <=2018)
    • year(+>2012 +<=2018)

    (6)通配符查询、正则、模糊匹配与近似查询

    通配符查询:不推荐,很费性能

    ​ ?代表1个字符,*表示0个或者多个字符

    • title:mi?d
    • title:be*

    正则

    • title:[bt]oy

    模糊匹配与近似查询

    • title:beautifl~1 :比如 本来应该是 beautiful,但是我们少打了一个 ful 打成了 fl;用模糊查询,可以自动识别出 beautiful
    • title:"lord rings"~2:比如 lord of the rings 就会被搜索出来

    Request body search(Query DSL)

    将查询语句通过 http Request body 发送给 ES

    (1)一般形式

    image-20210416153127193

    (2)分页

    image-20210416153244645

    • From 默认从0开始,默认返回10个结果;
    • size 表示获取多少个结果;
    • 上图中的分页表示,从10开始,获取后面的20个结果
    • 注意,获取靠后的翻页成本较高

    (3)排序

    image-20210416153508949

    如上图,对搜索结果进行了排序

    1. 最好在 数字类型、日期类型 字段上排序
    2. 因为对于多值类型或分析过的字段排序,系统会选一个值,无法得知该值

    (4)_source filtering

    _source 里面包含了该文档所有内容

    image-20210416162356144

    过滤:

    • 如果_source 没有存储,那么就只返回匹配的文档的元数据
    • 是 _source 支持使用通配符,如 "_source":["name*","desc*"]

    (5)脚本字段(拼接、计算)

    image-20210419160357020

    7.11测试

    image-20210419164435303

    (6)query match 与 match_phrase

    (1)query match

    image-20210419160600582

    • 如果是直接写,如上图中的上半区,那么会是两个term 都以 or 的形式出来;即包含 Last 或者 包含 Christmas 的;

    • 下半区,可以加上操作符 operator:and 就表示是两个分词是 and ,要同时包含才行;

    (2)query match_phraseimage-20210419160930367

    • 如果我们直接查,不加slop 参数,则默认是2个词要连在一起才出来,比如 aaa one love;
    • 如果我们加上 slop:1 参数,则 如上图,One I Love 也会被检索出来 (类似于 title:beautifl~1 )

    (7)query string query / simple query string

    (1)query string query

    image-20210419161254260

    • 这里面的 AND 是逻辑符
    • 在右图中,也可以使用分组

    (2)simple query string

    image-20210419161345483

    • 类似 Query String,但是会忽略错误的语法,同时只支持部分语法
    • 不支持在 query 中直接使用 AMD OR NOT ,会当做字符串处理
    • Term之间的默认关系是 OR,可以指定 Operator
    • 支持 部分逻辑
      • + 替代 AND
      • | 替代 OR
      • - 替代 NOT

    (8)query(exist,prefix,wildcard,regexp,ids)

    • Term query 精准匹配查询(查找号码为23的球员)
    • Exsit Query 在特定的字段中查找空值的文档(查找队名空的球员)
    • Prefix Query 查找包含带有指定前缀term的?档(查找队名以Rock开头的球员)
    • Wildcard Query 支持通配符查询,*表示任意字符,?表示任意单个字符(查找?箭队的球员)
    • Regexp Query 正则表达式查询(查找?箭队的球员)
    • Ids Query(查找id为1和2的球员),这个id为 _id 元数据

    Dynamic-Mapping

    (1)什么是 Mapping

    Mapping 类似于数据库中的schema的定义,作用如下

    • 定义索引中的字段的名称
    • 定义字段的数据类型,例如字符串,数字,布尔....
    • 字段,倒排索引的相关配置,(Analyzed or Not Analyzed,analyzer)

    Mapping会把 JSON文档映射成 Lucene 所需要的扁平格式

    一个Mapping 属于一个索引的 Type

    • 每个文档都属于一个Type
    • 一个Type有一个 Mapping 定义
    • 7.0开始不需要在 Mapping 定义中指定 Type,因为有且只有一个,那就是_doc

    (2)字段的数据类型

    image-20210419162610662

    (3)Dynamic-Mapping

    • 就是说当写入文档时,如果索引不存在,会自动创建索引;
    • 无需手动定义,ES会自动根据文档信息,推算出字段类型
    • 但有时候不一定对,比如地理位置
    • 当类型如果设置的不对,会导致一些功能无法使用,比如无法对字符串类型使用 range 查询

    可以通过 GET /users/_mapping

    image-20210419165724929

    能否更改 Mapping 字段类型?

    • 情况1:新增加字字段
      1. Dynamic 默认为 True:新增字段可增加,数据可被索引,Mapping 也更新
      2. Dynamic 如果为 False:新增字段无法被检索,文档可被搜索,_source中也有,但Mapping 无法被更新
      3. Dynamic 设置为 Strict:不符合当前Mapping 的新文档无法被写入
    • 对易游字段,一旦已经有数据写入,就不再支持修改字段定义
      1. Lucene 实现的倒排索引,一旦生成后,就不允许修改
    • 如果希望改变字段类型,必须Reindex API 重建索引
    • 原因
      1. 如果修改了字段的数据类型,会导致已被索引的术语无法被搜索
      2. 但如果是增加新的字段,就不会有这样的影响

    自定义 Mapping

    (1)建议

    • 可以参考API手册,纯手写
    • 为了减少出错率,提高效率,可以按照下列步骤
      1. 创建一个临时的 index,写入一些样本数据
      2. 通过访问 GET /indexname/_mapping 获取该临时索引的动态 Mapping定义
      3. 自定义修改,达到自己想要的效果并创建自己的索引
      4. 删除临时索引

    (2)控制当前字段是否被索引

    index - 控制当前字段是否被索引。默认为 ture。如果设置为 false,则该字段不可被搜索,如下面代码中的 mobile 字段

    PUT users5
    {
    	"mappings":{
    		"properties":{
    			"firstName":{"type":"text"},
    			"lastName":{"type":"text","index_options:"offsets"},
    			"mobile":{"type":"text","index":false}
    		}
    	}
    }
    

    (3)Index Options

    有4种不同级别的配置,可以控制倒排索引的内容

    • docs - 记录 doc id
    • freqs - 记录 doc id 和 term frequencies
    • positions - 记录 doc id / term frequencies / term position
    • offsets - 记录 doc id / term frequencies / term position / character offects

    Text类型,默认记录 positions,其他默认为 docs

    记录的内容越多,占用的存储空间越大

    配置:如(2)中的 "lastName":{"type":"text","index_options:"offsets"}

    (3)null_value

    如果有需要对 NULL 值实现搜索,那就要使用 null_value:"null",且只有 keyword 类型支持设置 Null_value

    在mapping中设置如下:

    ​ "messages":{"type":"keyword","null_value":"NULL"}

    (4)ES7中 copy_to 替代 _all

    • _all 在 7 中被 copy_to 所替代
    • 满足一些特定的搜索需求
    • copy_to 将字段的数值拷贝到目标字段,实现类似 _all 的作用
    • copy_to 的目标字段不出现在 _source 中
    • 如下列代码,就可以用 fullname 来搜索 这两个字段;
    PUT users5
    {
    	"mappings":{
    		"properties":{
    			"firstName":{"type":"text","copy_to":"fullName"},
    			"lastName":{"type":"text","copy_to":"fullName"}
    		}
    	}
    }
    

    (5)数组

    POST users5/_doc/
    {
      "firstName":["tom","tony"],
      "lastName": "jack"
    }
    
    

    发现结果,数组字段,依旧是 text 类型

    (6)多字段类型

    image-20210420173122596

    (7)keyword 与 text 区别

    Excat values VS Full Text

    • Exact Value: 包括数字 / 日期 / 具体一个字符串(例如 "app Store")
      • 其实就是 ES 中的 Keyword 类型,不需要分词,全内容为索引内容
    • 全文本,非结构化的全文本数据
      • ES 中的 text ,一般针对该类字段做 分词

    Index-Template 和 Dynamic-Template

    (1)Index-Template 介绍

    • Index Templates:帮助你设定 Mappings 和 Settings ,并且按照一定的规则,自动匹配到新创建的索引之上

      1. 模板仅在一个索引被新创建时,才会起作用。修改模板不会影响已创建的索引
      2. 你可以设定多个索引模板,这些设置会被 " merge " 在一起
      3. 你可以指定 " order " 的数值,控制 "merging " 的过程
    • 演示

    PUT _template/template_default
    {
      "index_patterns": ["*"],
      "order": 0
      , "version": 1
      , "settings": {
        "number_of_replicas": 1
        , "number_of_shards": 1
      }
    }
    
    PUT _template/template_test
    {
      "index_patterns": ["test*"]
      , "order": 1
      , "settings": {
        "number_of_shards": 1
        , "number_of_replicas": 2
      }
      , "mappings": {
        "date_detection": false
        , "numeric_detection": true
      }
    }
    
    • date_detection:默认情况下,是否在字符串中的日期,自动转换为日期数据类型
    • numeric_detection:默认情况下,字符串如果是纯数字字符串,是否自动转换成数字类型

    (2)Index Template 的工作方式

    • 当一个索引被新建时
      1. 应用 ES 默认的 settings 和 mappings
      2. 应用 order 数值低的 Index Template 中的设定
      3. 应用 order 高的 Index Template中的设定,之前的设定会被覆盖
      4. 应用创建索引时,用户显示指定的 Settings 和 Mappings,并覆盖之前模板中的设定

    (3)Index Template 演示案例

    image-20210421104553185

    如上图,我们发现真的应用了 template_test 模板的 mapping

    如下图,我们发现真的应用了 template_test 模板的 setting

    image-20210421104653792

    疑惑:为什么不应用 template_default 模板 呢?

    1. 因为我们之前上面(2)中说了,会先应用 order 低的,再应用 order 高的,且高的配置会覆盖低的
    2. 所以,template_test 的 order 是 1 ,比 template_default 的 order 高,所以 test 开头的索引,会应用 template_test的配置,覆盖 template_default 上的配置;

    (4)Dynamic Template 介绍

    • 根据 ES 识别的数据类型,结合字段名称,来动态设定字段类型

    • 比如说:

      1. 所有的字符串类型都设置成 Keyword,或者关闭 Keyword 字段
      2. is 开头的字段都设置成 boolean
      3. long_开头的都设置成 long 类型
    • 基本形式参考如下:

    • PUT test4
      {
        "mappings": {
          "dynamic_templates":[
            {
              "string_as_boolean":{
                "match_mapping_type":"string",
                "match":"is*",
                "mapping":{
                  "type":"boolean"
                }
              }
            },
            {
              "string_as_keyword":{
                "match_mapping_type":"string",
                "mapping":{
                  "type":"keyword"
                }
              }
            }
            ]
        }
      }
      
      PUT test4/_doc/1
      {
        "user":"zhangsan",
        "isVip":"true"
      }
      
      GET test4/_mapping
      

    (5)Dynamic Template 演示案例

    image-20210421111104546

    如上图,证明我们配置成功!

    其他案例:

    DELETE test4
    PUT test4
    {
      "mappings": {
        "dynamic_templates":[
          {
            "full_name":{
              "path_match":"name.*",
              "path_unmatch":"*.middle",
              "mapping":{
                "type":"text",
                "copy_to":"full_name"
              }
            }
          }
          ]
      }
    }
    
    PUT test4/_doc/1
    {
      "name":{
        "first":"john",
        "middle":"winston",
        "last":"lennon"
      }
    }
    
    GET test4/_search?q=full_name:lennon
    

    【2】搜索、结构化搜索

    聚合(Aggregation)的简介

    (1)聚合的介绍

    • ES 除搜索外,提供的针对 ES 数据进行统计分析的功能
      1. 实时性高
      2. Hadoop(T+1):也就是说如果是 Hadoop 来分析,怕是要一整天
    • 通过聚合,我们会得到一个数据的概览,是分析和总结全套的数据,而不是寻找单个文档
      1. 尖沙咀和香港岛的可烦数量
      2. 不同的价格区间,可预订的经济型酒店和五星级酒店的数量
    • 高性能,只需要一条语句,就可以从 ES 得到分析结果
      1. 无需在客户端自己去实现分析逻辑

    (2)聚合的分类

    • Bucket Aggregation:一些列满足特定条件的文档的集合
    • Metric Aggregation:一些数据运算,可以对文档字段进行统计分析
    • Pipeline Aggregation:对其他的聚合结果进行二次聚合
    • Matrix Aggregration:支持对多个字段的操作,并提供一个结果矩阵

    Bucket:一组满足条件的文档:可以初步理解是关系型数据库 group by 后面的

    Metric:一些系统的统计方法:可以理解成是关系型数据库中的聚合运算,如 count() max(),stats 包含 count,min,max,avg,sum

    (3)Bucket 演示案例

    这个就相当于 select * from group by column ,Bucket 就相当于是 group by 操作

    Bucket的例子,关键词是 aggs

    • 如果使用的聚合字段是 text 类型,那么它的聚合分组是 text 分词后的数据
    • 如果使用的聚合字段是 text 类型,那么它的 mapping 设置必须开启 fielddata 参数
    • 如:
    • image-20210429093915571
    • 常规的形式如下:
    GET users4/_search
    {
      "size":0,
      "aggs": {
        "user_group": {
          "terms": {
            "field": "user"
            "size":3 #这里面也可以写 size,如果是3,那就去分组后,前三行
          }
        }
      }
    

    image-20210421153305429

    如下图:查询年龄大于20的 根据Job字段分组查询

    image-20210429151131405

    如果是text类型:

    • 如下图,第一个查询中 job 字段为 text 类型,查出的结果会根据 job 内容分词后聚合查询
    • 如下图,第二个查询中,写的是 job.keyword,这样的话,就把整个 text 类型字段的文本做为一个单位来分组聚合,就不会分词了;

    image-20210429094140477

    不同工作类别中,查看年纪最大的3个员工的具体信息

    image-20210429095906927

    (4)优化 Term 聚合查询

    我们上面的信息,都可以称之为 Term 聚合查询

    image-20210429100427575

    • 可以通过在 keyword 字段上把 eager_global_ordinals 参数打开
    • 这样在有新数据写入时,会把新数据的 term 加入到缓存中来,这样再做 term aggs 的时候,性能会得到提升
    • 什么时候需要打开该参数?
      1. 聚合查询非常频繁
      2. 对聚合查询操作的性能要求较高
      3. 持续不断的高频文档写入

    (5)Metric 计算类聚合(max,min等)

    GET users4/_search
    {
      "size":0,
      "aggs": {
        "user_group": {
          "terms": {
            "field": "user"
          }
          , "aggs": {
              "max_age": {
                "max": {
                  "field": "age"
                }
              },
              "avg_age":{
                "avg":{
                  "field": "age"
                }
              }
            }
        }
      }
    }
    

    image-20210421162137347

    还可以相互嵌套!!

    (6)range 范围查询分桶

    image-20210429101431032

    (7)Histogram

    image-20210429150140818

    相当于SQL中的 where salary>=0 and salary <=100000 group by salary/5000

    (8)嵌套聚合

    image-20210429150350162

    相当于 select max,min,avg,sum,count from tab group by job

    image-20210429150421149

    相当于: select max,min,avg,sum,count from tab group by job,gender

    (9)Pipline: min_bucket

    主要是用来在一个聚合的结果集上 再次聚合

    image-20210429150809028

    (10)聚合分析的原理,精准度分析

    image-20210429151533872

    如下的 top 操作,就结果不一定准确,因为可能某一个节点包含了

    image-20210429151630466

    基于Term与text的搜索

    (1)基于 Term 的查询

    Term 是表达语意的最小单位

    特点:

    • Term Level Query / Term Query / Range Query / Exists Query / Prefix Query / Wildcard Query
    • 在ES 中,Term 查询,对输入不做分词。会将输入作为一个整体,在倒排索引中查找准确的词项,并且使用相关度算分公式,为每个包含该词项的文档进项相关度算分;
    • 可以通过 Constant Score 将查询转换成一个 Filtering ,避免算分,并利用缓存,提高性能

    基本形式:

    POST users4/_search
    {
      "query":{
        "term": {
          "user": {
            "value": "lisi"
          }
        }
      }
    }
    
    • 注意,大多数分词器会自动把分词信息转换为小写,如果这里写大写的 lisi ,就检索不到内容;
    • 而且,这里的 term 中的 value 已经是最小单位的一个整体,不会再拆分;

    完全匹配,当做 Keyword来匹配,如下:

    POST users4/_search
    {
      "query":{
        "term": {
          "user.keyword": {
            "value": "lisi"
          }
        }
      }
    }
    
    • 而且,针对与数组类型的多值字段,比如 a:["q","w"] 的字段;
    • term查询的值,是包含该值,而不是完全匹配
    • 解决方案:增加一个genre_count字段进行计数。会在组合 bool query 给出解决方案
      • image-20210425194059357

    (2)基于全文的查找(match query)

    其实包含:

    • Match Query / Match Phrase Query / Query String Query

    特点:

    • 索引和搜索时都会进行分词,需要查询的字符串会先传递到一个合适的分词器,然后生成一个供查询的词项列表
    • 查询的时候,会先对输入的查询进行分词,然后每个词项逐个进行底层的查询,最终将结果进行合并。并为每个文档生成一个算分; - 例如查 "hello world" ,会查到 包含 hellp 或者 world 的所有结果

    一般形式

    GET users4/_search
    {
      "query":{
        "match":{
          "user":{
            "query":"lisi wangwu"
            "operator":"OR"
            "mininum_should_match":2
          }
        }
      }
    }
    
    #多字段  multi_match
    GET /customer/doc/_search/
    {
      "query": {
        "multi_match": {
          "query" : "blog",
          "fields":  ["name","title"]   #只要里面一个字段包含值 blog 既可以
        }
      }
    }
    

    "mininum_should_match":2

    image-20210422153017516

    image-20210422153038000

    (3)Match query原理

    image-20210422152854916

    (4)复合查询 -- Constant Score 转为 Filter

    • 将 Query 转成 Filter ,忽略 TF-IDF 计算,避免相关性 score算分的开销

    • Filter 可以有效利用缓存

      image-20210425184300092

    (5)总结

    • 基于词项的查找 VS 基于全文的查找
    • 通过字段 Mapping 控制字段的分词
      • "Text" VS "Keyword"
    • 通过参数控制查询的 Precision & Recall
    • 复合查询 -- Constant Score 查询
      • 即使是对 Keyword 进行 Term 查询,同样会进行算分
      • 可以将查询转为 Filtering,取消相关性算分环节,提升性能

    相关性算分

    • 相关性 - Relevance
    • 搜索的相关性算分,描述了一个文档的查询语句匹配的程序。ES 会对每个匹配查询条件的结果进行算分 _score
    • 打分的本质是排序,需要把最符合用户需求的文档排在前面。ES 5 之前,默认的相关性算分采用 TF-IDF,之后采用BM25;

    (1)TF-IDF

    比如这么一段文本:区块链的应用

    被分为: 区块链 、 的 、 应用 ,三个 Term

    • TF(词频):Term Frequency:检索词在一篇文档中出现的评率 -- 检索词出现的次数 / 文档的总字数
      1. 度量一条查询和结果文档相关性的简单办法:简单讲搜索中每一个词的 TF 进行相加
        • TF(区块链) + TF(的) + TF(应用)
      2. Stop word
        • "的" 在文档中出现了很多次,但是对于贡献相关度几乎没有用处,所以不应该考虑他们的 TF
    • -----------------分割线 -------------------------
    • DF:检索词在所有文档中出现的频率

    image-20210425190518471

    评分公式:

    image-20210425192028414

    (2)BM 25

    • 从ES 5 开始,默认算法改为 BM 25
    • 和经典的 TF - IDF 相比,当 TF 无限增长时,BM 25 算分会趋于一个数值
    • 可以通过 explain api 查看 TF-IDF 的算分过程
      • PUT users/_search{ "explan":true , query:..... }

    (3)Boosting & Bootsting query 控制查询打分 image-20210425192920402

    image-20210426100950441

    (4)Bool 查询

    • 一个 bool 查询,是一个或者多个查询自居的组合
      • 总共包括4种自居,其中2种会影响算分,2种不影响算分
      • 可以嵌套查询,不同层次算分情况不同,越高层次算分越高
    • 相关性算分不只是全文检索的专利。也适用于 yes|no 的自居,匹配的自居越多,相关性评分越高;
    • 如果多条查询自居被合并为一条符合查询语句,比如 bool 查询,则每个查询自居计算得出的评分会被合并到总的相关性评分中;
    1. must :必须匹配,贡献算分

    2. should:选择性匹配,贡献算分

    3. must_not:Filer Context,查询子句,必须不能匹配

    4. filter:Filter Context,必须匹配,但不算贡献分

      image-20210425193742897

    【分布式】配置跨集群搜索

    (1)水平扩展的痛点

    • 单集群 - 当水平扩展石,节点数不能无限增加
      • 当集群的 meta 信息(元数据信息,节点、索引、集群状态)过多,会导致更新压力变大,单个 Active Master 会成为性能瓶颈,导致整个集群无法正常工作
    • 早期版本:通过Tribe Node 可以实现多集群访问的需求,但还是存在一定的问题
      • Tribe Node 会以 Client Node 的方式加入每个集群。集群中的 Master 节点的任务变更需要 Tribe Node 的回应才能继续
      • Tribe Node 不保存 Cluster state 的信息,一旦重启,初始化很慢
      • 当多个集群存在索引重名的情况,只能设置一种 Prefer 规则
    • ES 5.3 引入了跨集群搜索的功能(Cross Cluster Search),推荐使用
      • 允许任何节点扮演 federated 节点,以轻量的方式,将搜索请求进行代理
      • 不需要以 client Node 的形式加入其它集群
    • 集群配置如下图:

    image-20210427144305607

    1. 左边的是设置单个集群的主机发现信息(每个集群都需要操作设置),就是写集群包含了哪些节点,会搜索哪些节点
    2. 右边第一个是查询 cluster_one集群下的 tmdb,movies 索引
    3. 右边第二个是配置,当某个远程集群不可用了,就跳过搜索这个集群

    (3)CURL 发送ES 请求

    测试数据构造:

    • 在本机启动了 3个单实例 构造成 3个集群
    • 每个节点搜索

    image-20210427144755407

    (4)测试跨集群搜索

    image-20210427164343819

    【分布式】文档的分布式存储

    (1)文档在集群上的存储概述

    • 单个文档直接会存在(记住是作为一个整体单位) 某一个主分片和副本分片上
    • 文档到分片的映射算法
      1. 确保文档能均匀的分部在所用的分片,充分利用硬件资源,避免部分机器空闲,部分机器繁忙
      2. 潜在的算法
        • 随机 / Round Robin。当查询文档1,分片数很多的时候,需要多次查询才可能查到文档1(因为要扫描各个分片直到找到文档1所在的分片,和全表扫描似得)
        • 维护文档到分片的映射关系,当文档数量大的时候,维护成本高
        • 实时计算,通过文档1,自动算出需要去哪个分片上获取文档

    (2)文档到分片的路由算法(_routing)

    • shard = hash(_routing) % number_of_primary_shards

      1. hash 算法宝珠文档均匀分散到分片中

      2. 默认的 _routing 是文档的 id 值

      3. 可以自行制定 routing数值,例如用相同国家的商品,都分配到相同指定的 shard,例如下图:

        image-20210428145206964

      4. 设置 index Setting 后,primary 片数 不能随意修改,因为这个算法是得出的 hash值数字,%主分片数;一旦打乱则会找不到数据所在的正确分片,从而导致问题;

    (3)更新、删除一个文档的过程

    《1》更新一个文档 image-20210428145315898

    《2》删除一个文档

    image-20210428145345304

    【分布式】分片原理及其生命周期

    (1)分片的内部原理

    • 什么是 ES 分片?

      • ES 中最小的工作单元 / 是一个lucene 的 index
    • 一些问题:

      1. 为什么 ES 的搜索是近实时的(1S 后被搜到)
      2. ES 如何保证断电时数据也不会丢失
      3. 为什么删除文档,并不会立刻释放空间

    (2)倒排索引不可变性

    • 倒排索引采用 Immutabe Design , 一旦生成,不可更改
    • 不可变性,带来的好处如下:
      1. 无需考虑并发写文件的问题,避免了锁机制带来的性能问题
      2. 一旦读入内核的文件系统缓存,便留在那里。只要文件系统存有足够的空间,大部分请求就会直接请求内存,不会命中磁盘,提升了很大的性能
      3. 缓存容易生成和维护 / 数据可以被压缩
    • 不可变更性引起的问题:如果需要让一个新的文档可以被搜索,需要重建整个索引

    (3)Lucene Index(删除文档不会立即释放空间)

    • 在 Lucene 中,单个倒排索引文件被称为 Segment. Segment 是自包含的,不可变更的,多个 Segments 汇总一起,称为 Lucene 的 Index,其实对应的就是 ES 中的 Shard;
    • 当有新文档写入时,会生成新的 Segment。查询时会同时查询所有的 Segments,并且对结果汇总。Lucene 中有一个文件,用来记录所有的 Segments 信息,叫做 Commit Point
    • 删除文档的信息,保存在 .del 文件中,检索的都是会过滤掉 .del 文件中记录的 Segment
    • Segment 会定期 Merge,合并成一个,同时删除已删除文档

    image-20210428152738987

    (4)ES 的 Refresh(近实时)

    image-20210428153003892

    • 将index buffer 写入 Segment buffer 的过程叫做 Refresh. Refresh 不执行 fsync 操作
    • Refresh 频率:默认1s 发生一次,可以通过 index.refresh_interval配置。Refresh 后,数据就可以被搜索到了;这也是为什么ES 被称为近实时搜索;
    • 如果系统有大量的数据写入,那就会产生很多的 Segment
    • Index Buffer 被占满时,会触发 Refresh 默认值是 JVM 的 10%

    (5)ES 的 TransLog(断电不丢失)

    image-20210428154920514

    • Segment 写入磁盘的过程相对耗时,借助文件系统缓存, Refresh 时,先将Document信息 写入Segment 缓存,以开放查询
    • 为了保证数据不会丢失,所以在 index/操作 文档时,同时写 Tranaction Log,高版本开始,Transaction Log 默认落盘,每个分片有一个 Transaction Log
    • translog 是实时 fsync 的,既写入 es 的数据,其对应的 translog 内容是实时写入磁盘的,并且是以顺序 append 文件的方式,所以写磁盘的性能很高。只要数据写入 translog 了,就能保证其原始信息已经落盘,进一步就保证了数据的可靠性。
    • 在 ES Refresh 时, Index Buffer 被清空, Transaction log 不会清空;
    • 如果断电, Segment buffer 会被清空,这个时候就根据 Transaction log 来进行恢复
    • 操作参考
    index.translog.flush_threshold_ops,执行多少次操作后执行一次flush,默认无限制
    index.translog.flush_threshold_size,translog的大小超过这个参数后flush,默认512mb
    index.translog.flush_threshold_period,多长时间强制flush一次,默认30m
    index.translog.interval,es多久去检测一次translog是否满足flush条件
    
    index.translog.sync_interval 控制translog多久fsync到磁盘,最小为100ms
    index.translog.durability translog是每5秒钟刷新一次还是每次请求都fsync,这个参数有2个取值:request(每次请求都执行fsync,es要等translog fsync到磁盘后才会返回成功)和async(默认值,translog每隔5秒钟fsync一次)
    

    (7)ES 的 Flush

    image-20210428160740138

    • ES Flush & Lucene Commit
      1. 调用 Refresh,Index Buffer 清空并且 Refresh
      2. 调用 fsync,缓存中的 Segments 写入磁盘,且写入commit point 信息
      3. 清空(删除)旧的 Transaction Log
      4. 默认30分钟调用一次
      5. Transaction Log 满(默认 512MB)
      index.translog.flush_threshold_ops,执行多少次操作后执行一次flush,默认无限制
      index.translog.flush_threshold_size,translog的大小超过这个参数后flush,默认512mb
      index.translog.flush_threshold_period,多长时间强制flush一次,默认30m
      index.translog.interval,es多久去检测一次translog是否满足flush条件
      
    • 上面的参数是es多久执行一次flush操作,在系统恢复过程中es会比较translog和segments中的数据来保证数据的完整性,为了数据安全es默认每隔5秒钟会把translog刷新(fsync)到磁盘中,也就是说系统掉电的情况下es最多会丢失5秒钟的数据,如果你对数据安全比较敏感,可以把这个间隔减小或者改为每次请求之后都把translog fsync到磁盘,但是会占用更多资源;这个间隔是通过下面2个参数来控制的:
    index.translog.sync_interval 控制translog多久fsync到磁盘,最小为100ms
    index.translog.durability translog是每5秒钟刷新一次还是每次请求都fsync,这个参数有2个取值:request(每次请求都执行fsync,es要等translog fsync到磁盘后才会返回成功)和async(默认值,translog每隔5秒钟fsync一次)
    

    (8)ES 的 Merge

    • Segment 很多,需要被定期合并
      • 减少 Segments / 删除已经删除的文档
    • ES 和 Lucene 会自动进行 Merge 操作
      • 手动操作:POST my_index/_forcemerge

    【分布式】分布式查询

    (1)Query 阶段

    1. 用户发出搜索请求到 ES 节点(假设一共6个分配,3个 M 3个 S )
    2. ES 节点收到请求后,会以 coordinating 节点的身份,在6个中的其中3个分配,发送查询请求
    3. 被选中的分片执行查询,进行排序(算分排序)
    4. 然后每个分配都会返回 From + Size 个排序后的文档 ID 和排序值给 Coordinating 节点

    image-20210428174752724

    (2)Fetch

    1. Coordinating 节点将会从 Query阶段产生的数据;从每个分配获取的排序后的文档 id 列表、排序值列表,根据排序值重新排序。选取 From 到 From + Size 个文档的 Id
    2. 以 multi get 请求的方式,到想要的分片获取详细的文档数据

    image-20210428175031412

    (3)Query then Fetch 潜在的问题

    1. 性能问题
      • 每个分配上需要查的文档个数 = From + Size
      • 最终协调节点需要处理: number_of_shard * ( from + size )
      • 深度分页 引起的性能问题
    2. 相关性算分
      • 每个分配都 基于自己的分片上的数据 进行相关度计算。这回导致打分偏离的情况,特别是数据量很少的时候。
      • 相关性算分在分片之间是相互独立的。当文档总数很少的情况下,如果主分片大于1,主分片越多,相关性算分越不准

    (4)解决算分不准的问题

    1. 数据量不大的时候,可以将主分片设置为 1
      • 当数据量足够大的时候,只要保证文档均匀的分散在各个分片上,结果一般就不会出现偏差
    2. 使用 DFS Query Then Fetch
      • 搜索的 URL 中指定参数 "_search?search_type=dfs_query_then_fetch"
      • 到每个分配,把各个分片的磁盘和文档频率进行搜集,然后网站的进行一次相关性算分,耗费很多的CPU、内存 资源,执行性能低下,一般不建议使用

    sort 排序及 Doc-Values与Fielddata

    (1)排序的过程

    • 排序是针对字段原始内容进行的。倒排索引无法发挥作用
    • 需要用到正牌索引。通过文档 ID 和字段快速得到字段原始内容
    • ES 有两种实现方式(排序、聚合分析等都是依靠它)
      • Fielddata (一般用于开启text类型)
        1. 默认启用,可以通过 Mapping 设置关闭(增加索引的速度 / 减少磁盘空间)
        2. 如果重新打开,需要重建索引
        3. 明确不需要做排序、聚合分析等操作时,才手动关闭
        4. 手动关闭代码: 在 _mapping 下 properties 下 字段名下 "doc_values":"false"
        5. image-20210429093859094
      • Doc Values(默认开启,列式存储,对 Text 类型无效),ES2.X之后,默认使用它;
    • 关闭 Doc values

    (2)两种排序方式的对比

    image-20210428190653480

    分页与遍历:From-Size-Search-After-Scroll

    (1)基本分页形式

    image-20210428190916593

    • 默认情况下,按照相关度算分排序,返回前10条记录
    • 容易理解的分页关键词方案:
      • From :开始位置
      • Size:期望获取文档的数量

    (2)分布式系统中深度分页问题

    image-20210428191118715

    • 如上图,当一个查询: From = 990 , Size = 10

      1. 会在每个分配上都先获取1000个文档。

        然后在通过 Coordinating Node 聚合所有结果。最后在通过排序选取前1000和文档

      2. 页数越深,占用内存越多。为了避免深度分页带来的内存开销。ES 有一个设定,默认限定到 10000 个文档

        即 Index.max_result_window 参数

    (3)Search After 避免深度分页

    image-20210428192126789

    • 避免深度分页的性能问题,可以实时获取下一页的文档信息
      • 不支持指定页数( From )
      • 只能往下翻(局限性)
    • 第一步收缩需要指定 Sort,并且保证 Sort字段值是唯一的(可以如上图加上 _id 保证唯一性 )
    • 然后使用上一次,最后一个文档查询出结果的 Sort 值进行查询

    如何执行?

    1. 第一次运行,不需要加入 Search_after 参数,因为不知道其 sort值是多少

    image-20210428192455231

    第二次执行,就要把上次查询出来的 sort值,放入 search_after 关键字中;如上图

    Search After 是如何解决深度分页的问题的?

    1. 假设 Size 是 10
    2. 当查询 990 - 1000 时
    3. 通过 唯一排序值,快速利用正排索引定位文档读取位置,然后读取该位置下面的 10个文档,这样就将每次要处理的文档都控制在10

    (4)Scroll 查全索引

    image-20210428194211285

    • 这个时间其实指的是es把本次快照的结果缓存起来的有效时间。
      scroll 参数相当于告诉了 ES我们的search context要保持多久,后面每个 scroll 请求都会设置一个新的过期时间,以确保我们可以一直进行下一页操作。
    • 快照只能生成当前的,当你利用这个 scroll_id 一直往下滚动搜索的话,那么只能获取到使用 _search?scroll=5 的时候的快照数据,之后的数据是看不到的

    (5)总结:搜索类型和使用场景

    1. 默认的 Regular:
      • 需要试试获取顶部的部分文档。例如查询最新的订单
      • 默认 from = 0 , size = 10
    2. 滚动的 Scroll:
      • 需要全部文档,例如导出全部数据
      • 使用 Scroll = 5m ,快照生成
    3. 页码的 Pagination:
      • From 和 Size
      • 如果需要深度分页,则选用 Search After

    并发控制

    (1)并发控制的必要性

    例子:

    image-20210428195854348

    • 两个程序同时更新某个文档,如上图,两个程序同时修改某个文档的字段,ES是没有锁的,所以如果不做并发控制,会导致更新丢失问题
    • 悲观并发控制
      • 嘉定有变更冲突的可能,会对资源加锁,防止冲突。例如数据库行锁
      • 但我们知道 ES 是没有锁的,所以不会使用这个
    • 乐观并发控制
      • 嘉定冲突是不会发生的,不会阻塞正在尝试的操作。如果数据在读写中被修改,更新将会失败。应用程序决定如何解决冲突,例如重试更新,使用新的数据,或将错误报告给用户
      • ES 采用的是乐观并发控制

    (2)ES 的乐观并发控制

    image-20210428200307013

    • ES 中的文档是不可变更的。如果你更新一个文档,会将旧文档标记为删除,同时增加一个全新的文档。同时文档的 Version 字段加1
    • 内部版本控制
      • 使用: if_seq_no + if_primary_term
      • 如:PUT products/doc/1?if_seq_no=1&if_primary_term=1
    • 使用外部版本(使用其他数据库作为主要数据存储,如从mysql同步数据到 ES )
      • version + version_type = external
      • 如:PUT products/_doc/1?version=30000&version_type=external

    内部版本控制案例:

    image-20210428200540238

    • 如上图,不管是查询、还是更新,还是索引操作,都会显示 _seq_no 以及 _primary_term 元数据信息
    • 然后我们根据这个值,用 PUT products/doc/1?if_seq_no=1&if_primary_term=1 格式来判断该值,在本会话查询到提交更新之后是否有其他会话并发更新、索引;

    外部版本控制案例:

    image-20210428201226159

    • 如上图,是指定版本的,如果版本号已经存在,则报错
    • 所以我们可以先查询,然后以查询出的 version+1 作为更新参数,如果有并发在本回话之前更新了
    • 版本号是只能更高不能更低的
      • 如:
        • session 1:GET /products/doc/1 ,获取到 version=100
        • session 2:GET /products/doc/1 ,获取到 version=100 并发操作也获取到100
        • session 2:修改 count:为 1100,提交的版本号为 version+1 即 101,操作成功
        • session 1:修改 count:为 1001,提交的版本号为 version+1 即 101,更新操作发现当前版本号>=我们本次提交的版本号 101的了,所以会报错;
      • 最终这样实现了 乐观并发控制

    【总结】

    (1)【match,match_phrase,query_string,term,bool查询的区别】

    参考:https://blog.csdn.net/weixin_46792649/article/details/108055763
    参考:http://blog.majiameng.com/article/2819.html
    参考:https://www.pianshen.com/article/66431547985/
    首先,我们要明白 keyword 和 text类型的区别;
    《1》keyword:不参与分词 《2》text:参与分词
    所以:

    1. term:某个字段,完全匹配分词

      • 精确查询,搜索前不会再对搜索词进行分词
      • 如:"term":{ "foo": "hello world" }
      • 那么只有在字段中存储了“hello world”的数据才会被返回,如果在存储时,使用了分词,原有的文本“I say hello world”会被分词进行存储,不会存在“hello world”这整个词,那么不会返回任何值。
      • 但是如果使用“hello”作为查询条件,则只要数据中包含“hello”的数据都会被返回,分词对这个查询影响较大。
    2. match_phase:完全、精准匹配

      • 查询确切的phrase,keyword需要完全匹配,text需要完全匹配(多个分词均在且顺序相同)
      • 如果换个顺序,例如:有字符串 "深圳鹏开信息技术有限公司",分词为 深圳[0] , 鹏开[1] , 信息技术[2] , 信息[3] , 技术[4] , 有限公司[5] , 有限[6] , 公司[7];
      • 1.es会先过滤掉不符合的query条件的doc:
        • 在搜索"鹏开技术信息"时,被分词成 鹏开[0] , 技术信息[1] , 技术[2] , 信息[3]
        • 很明显,技术信息这个分词在文档的倒排索引中不存在,所以被过滤掉了
      • 2.es会根据分词的position对分词进行过滤和评分,这个是就slop参数,默认是0,意思是查询分词只需要经过距离为0的转换就可以变成跟doc一样的文档数据
        • 在搜索"鹏开信息"时,如果不加上slop参数,那么在原文档的索引中,"鹏开"和"信息"这两个分词的索引分别为1和3,并不是紧邻的,中间还存在一个"信息技术"分词,很显然还需要经过1的距离,才能与搜索词相同。所以会被过滤。
        • 那么我们加上slop参数就好了 {"query":"鹏开信息","slop":1}
    3. match:某个字段,出现一个或多个分词的模糊匹配

      • 先对输入值进行分词,对分词后的结果进行查询,文档只要包含match查询条件的一部分就会被返回。
    4. query_string:多个分词逻辑操作时使用,比如 与或非

      • 语法查询,同match_phase的相同点在于,输入的查询条件会被分词,但是不同之处在与文档中的数据可以不用和query_string中的查询条件有相同的顺序。
      • 可以使用
    5. bool:多字段多条件查询

      • 参数1:must 必须匹配
      • 参数2:must_not 必须不匹配
      • 参数3:should 默认情况下,should语句一个都不要求匹配,只有一个特例:如果查询中没有must语句,那么至少要匹配一个should语句

    【参考文档】

    本文参考学习笔记自:阮一鸣 极客时间教程

  • 相关阅读:
    LeetCode-5. Longest Palindromic Substring(M)
    Python if else简洁写法,列表推导式,三目运算符写法
    Java GC机制
    int与integer的区别
    Java内存分配机制
    HashMap原理
    哈希表算法
    哈希
    java 三大框架面试题
    Java反射机制
  • 原文地址:https://www.cnblogs.com/gered/p/14735410.html
Copyright © 2020-2023  润新知