zoukankan      html  css  js  c++  java
  • (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语句

    【参考文档】

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

  • 相关阅读:
    HTML元素解释
    Java命名规范
    HDU 1058 Humble Numbers(DP,数)
    HDU 2845 Beans(DP,最大不连续和)
    HDU 2830 Matrix Swapping II (DP,最大全1矩阵)
    HDU 2870 Largest Submatrix(DP)
    HDU 1421 搬寝室(DP)
    HDU 2844 Coins (组合背包)
    HDU 2577 How to Type(模拟)
    HDU 2159 FATE(二维完全背包)
  • 原文地址:https://www.cnblogs.com/gered/p/14735410.html
Copyright © 2011-2022 走看看