zoukankan      html  css  js  c++  java
  • 白日梦的ES笔记三:万字长文 Elasticsearch基础概念统一扫盲

    一、导读

    本篇是白日梦的第三篇ES笔记,前面已经跟大家分享过两篇ES笔记了,分别是:

    ES基础篇--快速上手ES

    ES进阶篇--50个检索、聚合案例


    其实这个专题相对来说质量还是比较不错的,看过前面两篇文章之后基本上大家可以上手使用ES了,包括对一些花里花哨的查询相关的写法也有所了解。然后这一篇文章会和大家调过头来重新巩固一下基础概念上的扫盲。


    二、彩蛋福利:账号借用


    三、ES的Index、Shard及扩容机制

    首先你看下这个表格(ES6):

    Elasticsearch 关系型数据库
    Document
    type(ES7中被取消)
    index Database

    在ES中的Index的地位相当于是MySQL中的database。所以你让ES帮你存储数据你总得先创建一个Index吧,如果你手动的定制创建Index,你还可以为Index指定shard。

    那什么是shard呢?下文马上说。

    下面是对Index操作的Case:

    # 创建索引
    PUT my_index
    { 
      # 设置index的shard信息
    	"settings": {
    		"number_of_shards": 3,
    		"number_of_replicas": 2
    	},
      # 设置index中各个字段的类型,属性(下文细讲)
      "mapping":{
        ... 
      }
    }
    
    # 修改索引
    PUT /my_index/_settings
    {
      # 只能改number_of_replicas,不能改number_of_shards
      "number_of_replicas":3
    }
    
    # 删除索引
    DELETE /my_index
    DELETE /my_index1,my_index2
    DELETE /my_*
    DELETE /_all # 删掉所有索引
    
    # 如果不想让ES可以一下子删除所有索引,可以通过配置文件设置
    elasticsearch.yml
    action.destructive_requires_name:true
    

    shard分为primary shard和replica shard ,其中的primary shard可以接受读/写请求,replica shard可以接受读请求,起到一个负载的作用。默认情况下我创建的索引都有: number_of_shards = 5 和number_of_replicas = 1。表示一共有五个primary shard,并且每个primary 都有一个副本。也就是 5+5*1 =10个shard。

    但是当你启动单台ES实例时,架构其实是下面这样:

    你会发现,其实系统中就有5个shard。不存在上面计算的10个shard。原因是因为ES要求Primary Shard 和它的备份 replica shard不能同时存在于一个Node上。所以你单个Node启动后,就只有5个primary shard。并且这时你去看集群的状态,会发现整个集群处于yellow状态,表示集群整体可用,但是存在replica shard不可用的情况。

    然后你会不会好奇,假设我有2个Node(两个ES实例)组成的ES集群,你怎样做,才能让系统中的Shard是如何负载均衡分布在两个Node上呢?

    回答:其实你不用操心,ES自己会帮你完成的。当你增加或减少节点时,ES会自动的进行rebalance,使数据平均分散在不同的节点中。

    举个例子:假设你真的又启动了一个Node,这个Node会自动的加入到上面那个ES中去,自动组成一个有两个Node的集群,如果你依然使用的默认配置即:number_of_shards = 5 和 number_of_replicas = 1。这时ES会自动将系统rebalance成下图这样:

    此时你再去看集群的状态,会发现为green。表示集群中所有shard都可用。

    Node2中会存在5个replica shard,他们是Node1中的Primary的备份。每个shard相当于是一个luncene实例,拥有完整的检索数据、处理请求的能力。所以shard的数量越多,一定意义上意味着ES的吞吐量就越大。

    但是你需要注意的是,primary shard的数量是不能改变的,但是它的副本的数量可以改变。

    至于为什么primary shard的数量是不能改变的,下文中的路由原理会说的。

    所以当你想对现在有的ES集群进行扩容的时,就存在两种选择:

    1、纵向扩容:你不改变集群的总shard数,然后去买配置更高,存储更大的机器跑这些shard。

    2、横向扩容:你扩大replica shard的数量,然后去多购置几个配置低的机器,你只需要写好配置文件,再启动Node,它自己会加入到现有的集群中。因为每个shard的都能对外提供服务嘛,所以你这样扩容系统的性能肯定有提升。

    根据现在云服务器实例的市场行情来看,方案二会更省钱一些。

    当然了如果你想让ES集群有最好的性能,还是使用默认的配置:number_of_shards = 5 和number_of_replicas = 1,这时你需要10台机器。每个集群上都启动一个ES实例,让这10个实例组建集群。就像下图这样:

    这时每个shard都独享操作系统的所有资源,性能自然会最好。

    四、ES支持的核心数据类型

    参考官网 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-types.html

    4.1、数字类型

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

    示例:

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "number_of_bytes": {
              "type": "integer"
            },
            "time_in_seconds": {
              "type": "float"
            },
            "price": {
              "type": "scaled_float",
              "scaling_factor": 100
            }
          }
        }
      }
    }
    

    4.2、日期类型

    date
    

    示例:

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "birthday	": {
              "type": "date" 
            }
          }
        }
      }
    }
    
    PUT my_index/_doc/1
    { "date": "2015-01-01" } 
    

    4.3、boolean类型

    string类型的字符串可以被ES解释成boolean。

    boolean
    

    示例:

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "is_published": {
              "type": "boolean"
            }
          }
        }
      }
    }
    

    4.4、二进制类型

    binary
    

    示例

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "name": {
              "type": "text"
            },
            "blob": {
              "type": "binary"
            }
          }
        }
      }
    }
    
    PUT my_index/_doc/1
    {
      "name": "Some binary blob",
      "blob": "U29tZSBiaW5hcnkgYmxvYg==" 
    }
    

    4.5、范围

    integer_range、float_range、long_range、double_range、date_range
    

    示例

    PUT range_index
    {
      "settings": {
        "number_of_shards": 2
      },
      "mappings": {
        "_doc": {
          "properties": {
            "expected_attendees": {
              "type": "integer_range"
            },
            "time_frame": {
              "type": "date_range", 
              "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
            }
          }
        }
      }
    }
    
    PUT range_index/_doc/1?refresh
    {
      "expected_attendees" : { 
        "gte" : 10,
        "lte" : 20
      },
      "time_frame" : { 
        "gte" : "2015-10-31 12:00:00", 
        "lte" : "2015-11-01"
      }
    }
    

    4.6、复杂数据类型

    对象类型,嵌套对象类型
    

    示例:

    PUT my_index/_doc/1
    { 
      "region": "US",
      "manager": { 
        "age":     30,
        "name": { 
          "first": "John",
          "last":  "Smith"
        }
      }
    }
    

    在ES内部这些值被转换成这种样式

    {
      "region":             "US",
      "manager.age":        30,
      "manager.name.first": "John",
      "manager.name.last":  "Smith"
    }
    

    4.7、Geo-type

    ES支持地理上的定位点。

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "location": {
              "type": "geo_point"
            }
          }
        }
      }
    }
    
    PUT my_index/_doc/1
    {
      "text": "Geo-point as an object",
      "location": { 
        "lat": 41.12,
        "lon": -71.34
      }
    }
    
    PUT my_index/_doc/4
    {
      "text": "Geo-point as an array",
      "location": [ -71.34, 41.12 ] 
    }
    
    

    五、精确匹配与全文检索

    精确匹配和全文检索是ES提供的两种检索方式,都不难理解。

    5.1、精确匹配:exact value

    搜索时输入的value必须和目标完全一致才算作命中。

    "query": {
    	  # match_phrase 短语精确匹配的关键字
        # 只有name字段 完全等于 “白日梦”的doc 才算命中然后返回
    		"match_phrase": { 
    				"name": "白日梦" 
    		 } 
     } 
    

    5.2、全文检索:full text

    全文检索时存在各种优化处理如下:

    • 缩写转换: cn == china
    • 格式转换 liked == like == likes
    • 大小写转换 Tom == tom
    • 同义词转换 like == love

    示例

    GET /_search
    {
        "query": {
          	# match是全文检索的关键字
            # 白日梦可以被分词器分成:白、白日、白日梦
            # 所以当你使用:白、白日、白日梦、我是白日梦、白日梦是我 等等词条检索,都可以检索出结果
            "match" : {
                "name" : "白日梦"
            }
        }
    }
    

    六、倒排索引 & 正排索引

    6.1、倒排索引 inverted index

    其实正排索引和倒排索引都是人们取的名字而已。主要是你理解它是什么东西就好了。

    正排索引:以doc为维度,记录doc中出现了哪些词。

    倒排索引:以把doc打碎成一个个的词条,以词语为维度。记录它在哪些doc中出现过。

    倒排索引要做的事就是将一篇文章通过分词器打散成很多词,然后记录各个词分别在哪篇doc中出现过。用户在使用的时候输入一串搜索串,这串字符串同样会使用一样的分词器打散成很多词。再拿着这些词去方才建立的倒排索引中匹配。同时结合相关性得分找到。

    假设我们存在这样两句话。

    doc1 : hello world you and me
    doc2 : hi world how are you
    

    建立倒排索引就是这样

    词条 doc1(*表示出现过) doc2(-表示不曾出现过)
    hello * -
    world * *
    you * *
    and * -
    me * -
    hi - *
    how - *
    are - *

    这时,我们拿着hello world you 来检索,同样需要先经过分词器分词,然后可以得到分出来的三个单词:hello、world、you,然后拿着这三个单词去上面的倒排索引表中找,你可以看到:

    • hello在doc1中出现过。

    • world在doc1、doc2中出现过。

    • you在doc1、doc2中出现过。

    最终doc1、doc2都会被检索出,但是doc1命中了更多的词,因此doc1得分会更高,排名越靠前。

    6.2、正排索引 doc value

    doc value 是指所有不分词的document的field。

    在建立索引的时候,一方面会建立倒排索引,以供搜索用。一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用。

    正排索引大概长这样:

    document name age
    doc1 张三 12
    doc2 李四 34

    os cache会缓存正排索引,以提高访问doc value的速度。当OS Cache中内存大小不够存放整个正排索引时,doc value中的值会被写入到磁盘中。

    关于性能方面的问题:ES官方建议,大量使用OS Cache来进行缓存和提升性能。不建议使用jvm内存来缓存数据,那样会导致一定的gc开销,甚至可能导致oom问题。所以官方的建议给JVM更小的内存,给OS Cache更大的内存。假如我们的机器64g,只需要给JVM 16g即可。

    6.3、禁用doc value

    假设我们不使用聚合、排序等操作,为了节省空间,在创建mappings时,可以选择禁用doc value,不创建正排索引。

    PUT /index
    {
        "mappings":{
            "my_type":{
                "properties":{
                    "my_field":{
                        "type":"text",
                        "doc_values":false # 禁用doc value
                    }
                }
            }
        }
    }
    

    七、简述相关性评分

    relevance score 相关度评分算法, 直白说就是算出一个索引中的文本和搜索文本之间的相似程度。

    Elasticsearch使用的是 TF-IDF算法 (term-frequency / inverser document frequency)。

    • term-frequency: 表示你搜索的词条在当前doc中出现的次数,出现的次数越多越相关。
    • inverse document frequency : 表示搜索文本中的各个词条在整个index中所有的document中出现的次数,出现的次数越多越不相关。
    • field-length: field长度越长,越不相关。

    八、分词器

    ES官网分词器模块 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/analysis.html

    8.1、什么是分词器?

    我们使用分词器可以将一段话拆分成一个一个的单词,甚至可以进一步对分出来的单词进行词性的转换、时态的转换、单复数的转换的操作。

    为什么使用分词器呢?

    你想一个doc那么长,成千上万字。为了对它进行特征的提取,分析。就得把它还原成组成它的词条。这样会提高检索时的召回率,让更多的doc被检索到。

    8.2、分词器的组成

    character filter:

    在一段文本在分词前先进行预处理,比如过滤html标签, 将特殊符号转换成123..这种 阿拉伯数字等特殊符号的转换。

    tokenizer:

    进行分词、拆解句子、记录词条的位置(在当前doc中占第几个位置term position)及顺序。

    token filter:

    进行同义词的转换,去除同义词,单复数的转换等等。

    ES内置的分词器:

    • standard analyzer(默认)
    • simple analyzer
    • whitespace
    • language analyzer(特定语言的分词器,English)

    另外比较受欢迎的中文分词器为IK分词器,这个分词器的插件包、安装方式我都整理成文档了,公众号后台回复:es即可领取。

    8.3、修改Index使用的分词器

    PUT /my_index
    {
      "settings":{
        "analysis":{
          "analyzer":{
            "es_std":{
              # 指定分词器的类型是:standard
              "type":"standard",
              # 指定分词器的停用词:_english_
              "stopwords":"_english_"
            }
          }
        }
      }
    }
    

    九、mapping

    9.1、认识mapping

    看到这里你肯定知道了,我们想往ES中写数据是需要一个index的。其实我们在往ES中PUT数据之前是可以手动创建Mapping,这里的mapping其实好比你搞一个java类,做一次对数据结构的抽象,比如name 的类型是String,age的类型是Integer。

    就好比下面这样:

    PUT my_index
    { 	
      # 指定index的primary shard数量以及 replicas的数量
      “settings”:{
        "number_of_shards":1,
        "number_of_repicas":0
      },
       # 关键字,我们手动自定my_index中的mapping
      "mappings": {
        "my_index": { # index的名称
          "properties": { # 关键字,mapping的属性,字段
            "my_field1": { # 相当于Java中的   String my_field1
              "type": "text",
               "analyzer":"english"# 指定分词器,说明这个字段需要分词建立倒排索引
            },
            "my_field2": { # 相当于Golang中的 var my_field2 float
              "type": "float",
    		      # 指定是否要分词。analyzed表示要,not_analyzed表示不要
      				"index":"not_analyzed"
            },
            "my_field3": {
              "type": "scaled_float",
              "scaling_factor": 100
            }
          }
        }
      }
    }
    

    1、mapping json中包含了诸如propertiesmatadata(_id,_source,_type)settings(analyzer)以及其他的settings。

    2、我们把上面的json中的properties部分称为:root object

    3、自己创建mapping一般是为了更好的控制各个字段的数据类型,包括使用到的分词器。

    4、另外注意:field的mapping只能新增,不能修改。

    你也可以在往ES中PUT数据之前不创建任何Mapping,ES会自动为我们生成mapping。就像下面这样,自动生成的mapping信息叫做dynamic mapping,下文中我们还会详细讲这个dynamic

    PUT my_index/_doc/1
    {
      "title": "This is a document"
    }
    

    9.2、查看mapping

    # 查看某个index下的某个type的mapping
    GET /index/_mapping/type
    
    # 查看某个index的mapping
    GET /index/_mapping
    

    9.3、dynamic mapping (动态mapping)

    就像下面这样,我们直接往ES中PUT数据,ES在为我们创建index时就会自动生成dynamic mapping。其实用大白话讲就是ES自动推断你往它里面存的json串的类型。比如下面的"first_name"会被dynamic mapping成string 类型的。

    PUT my_index/_doc/1
    {
      "first_name": "John"
    }
    

    ES使用_type来描述doc字段的类型,原来我们直接往ES中存储数据,并没有指定字段的类型,原因是ES存在动态类型推断(ES支持的类型上文中我们也一起看过了,如果不记得阔以再去看一下哈)。默认的mapping中定义了每个field对应的数据类型以及如何进行分词。

    null          --> no field add
    true flase    --> boolean
    123           --> long
    123.123       --> double
    1999-11-11    --> date
    "hello world" --> string
    Object        --> object
    

    9.4、定制dynamic mapping 策略

    • ture: 语法陌生字段就进行dynamic mapping。
    • false: 遇到陌生字段就忽略。
    • strict: 遇到默认字段就报错。

    示例

    PUT /my_index/
    {
        "mappings":{
            "dynamic":"strict"
        }
    }
    
    • 禁用ES的日期探测的Demo
    # 创建mapping并制定:禁用ES的日期探测
    PUT my_index
    {
      "mappings": {
        "_doc": {
          "date_detection": false
        }
      }
    }
    
    # 添加一条doc
    PUT my_index/_doc/1 
    {
      "create": "1985/12/22"
    }
    
    # 查看doc,结果如下
    GET my_index/_doc/1 
    {
      "_index": "my_index",
      "_type": "_doc",
      "_id": "1",
      "_version": 1,
      "found": true,
      "_source": {
        "create": "1985/12/22"
      }
    }
    
    # 查看mapping 
    GET my_index/_mapping
    # 结果如下:
    {
      "my_index": {
        "mappings": {
          "_doc": {
            "date_detection": false,
            "properties": {
              "create": {
                # 被任务是text类型
                "type": "text",
                # ES会自动帮你创建的下面的field部分
                # 即 create是text类型,create.ketword是keyword类型
                # keyword类型不会分词,默认保留前256字符
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        }
      }
    }
    
    • 定制日期发现规则
    PUT my_index
    {
      "mappings": {
        "_doc": {
          "dynamic_date_formats": ["MM/dd/yyyy"]
        }
      }
    }
    
    PUT my_index/_doc/1
    {
      "create_date": "09/25/2015"
    }
    
    • 定制数字类型的探测规则
    PUT my_index
    {
      "mappings": {
        "_doc": {
          "numeric_detection": true
        }
      }
    }
    
    PUT my_index/_doc/1
    {
      "my_float":   "1.0",
      "my_integer": "1" 
    }
    

    定制type field

    ES中type相当于MySQL的数据表嘛,ES中可以给现存的type添加field。但是不能修改,否则就会报错。

    type在高版本的ES7中被废弃了,Index的概念依然保留着。

    # 创建index:twitter
    PUT twitter
    {
      "mappings": {
        # user为type
        "user": {
          "properties": {
            "name": {
             # 会被全部检索
            "type": "text" ,
             # 指定当前field使用 english分词器
            "analyzer":"english" 
            },
            "user_name": { "type": "keyword" },
            "email": { "type": "keyword" }
          }
        },
        "tweet": {
          "properties": {
            "content": { "type": "text" },
            "user_name": { "type": "keyword" },
            # "tweeted_at": { "type": "date" },
            "tweeted_at": {
            "type": "date" 
             # 通过index设置为当前field  tweeted_at不能被分词
             "index": "not_analyzeed" 
            }
          }
        }
      }
    }
    

    9.5、mapping复杂数据类型在底层的存储格式

    Object类型

    # object类型的json
    {
        "address":{
            "province":"shandong",
            "city":"qingdao"
        },
        "name":"bairimeng",
        "age":"12"
    }
    
    # ES会将上面的json转换成如下的格式存储
    {
        "name" : [bairimeng],
        "age" : [12],
        "address.province" : [shandong]
        "address.city" : [qingdao]
    }
    

    Object数组类型

    # Object数组类型
    {
        "address":[
            {"age":"12","name":"张三"},
            {"age":"12","name":"张三"},
            {"age":"12","name":"张三"}
        ]
    }
    
    # ES会将上面的json转换成如下的格式存储
    {
        "address.age" : [12,12,12],
        "address.name" : [张三,张三,张三]
    }
    

    9.6、ES7中废弃了type的概念

    在一开始我们将ElasticSearch的index比作MySQL中的database,将type比作table,其实这种类比是错误的。因为在MySQL中不同表之间的列在物理上是没有关系的,各自占有自己的空间。

    但是在ES中不是这样,可能type=Student中的name列和type=Teacher中的name列会被lucene认为是同一个field。导致Lucene处理效率下降。

    所以在ES7中直接就将type概念废弃了。

    不过你也不用担心,大部分企业都倾向于使用低版本的ES,比好比你现在用的依然是java8 而不是JDK14。

    9.7、认识一些mate-field(元数据字段)

    这里说的元数据字段指定的是,当你检索doc时,除了返回的doc本身的数据之外,其他的出现在检索结果中的数据,我们是需要了解这些字段都是什么含义的。如下:

    _index , _type , _id , _source , _version

    _id

    它是document的唯一标识信息。上图中我手动指定了id等于1。如果不指定的话,ES会自动为我们生成一个长20个字符的id,ES会保证集群中的生成的doc id不会发生冲突。 有这种场景,比如你的数据是从MySQL这种数据库中倒入进ES的,那其实完全可以使用MySQL中的数据行的ID作为doc id。

    _index

    你可以简单粗暴的将es的index的地位理解成MYSQL中的数据库。这里的元数据_index被用来标识当前的doc存在于哪个index中。index的命名规范,名称小写,不能用下划线开头,不能包含逗号。

    ES支持跨域index进行检索

    详情见官网 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-index-field.html

    _type

    这个字段用来标识doc的类型。但它其实是一个逻辑上的划分。

    field中的value在顶层的lucene建立索引的时候,全部使用的opaque bytes类型,不区分类型的lucene是没有type概念的。

    为了方便我们区分出不通doc的类型,于是在document中加了一个_type属性。

    ES会通过_type进行type的过滤和筛选,一个index中是存放的多个type实际上是存放在一起的,因此一个index下,不可能存在多个重名的type。

    _version `_version`是doc的版本号,可以用来做并发控制,当一个doc被创建时它的`_version`是1,之后对它的每一次修改,都会使这个版本号+1,哪怕是你将这个doc删除了,这个doc的版本号也会增加1。

    _source

    通过这个字段可以定制我们想要返回字段。比如说一个type = user类型的doc中存在100个字段,但是可能前端并不是真的需要这100个字段,于是我们使用_source去除一些字段,注意和filter是不一样的,因为filter不会影响相关性得分。

    你可用像下面这样禁用_source

    PUT tweets
    {
      "mappings": {
        "_doc": {
          "_source": {
            "enabled": false
          }
        }
      }
    }
    

    _all

    首先它也是一个元数据,当我们往ES中插入一条document时。ES会自动的将这个doc中的多个field的值串联成一个字符串,然后用这个作为_all字段的值并建立索引。当用户发起检索却没有指定从哪个字段查询时,默认就会在这个_all中进行匹配。

    _field_names

    举个例子说明这个属性怎么用:

    首先往index=my_index的索引下灌两条数据

    # Example documents
    PUT my_index/_doc/1
    {
      "title": "This is a document"
    }
    
    PUT my_index/_doc/2?refresh=true
    {
      "title": "This is another document",
      "body": "This document has a body"
    }
    

    然后像下面这样使用_field_names检索,并且指定了字段=“title”。此时ES会将所有包含title字段,且title字段值不为空的doc检索出来。

    GET my_index/_search
    {
      "query": {
        "terms": {
          "_field_names": [ "title" ] 
        }
      }
    }
    

    禁用_field_names:

    PUT tweets
    {
      "mappings": {
        "_doc": {
          "_field_names": {
            "enabled": false
          }
        }
      }
    }
    

    _routing

    下面路由导航中细说。

    _uid

    在ES6.0中被弃用。

    9.8、copy_to

    在上一篇文章中跟大家介绍过可以像下面这样跨越多个字段搜索

    # dis_max
    GET /your_index/your_type/_search
    {   
        # 基于 tie_breaker 优化dis_max
        # tie_breaker可以使dis_max考虑其它field的得分影响
        "query": { 
         # 直接取下面多个query中得分最高的query当成最终得分
         # 这也是best field策略
         "dis_max": { 
            "queries":[
               {"match":{"name":"关注"}},
               {"match":{"content":"白日梦"}}
            ],
            "tie_breaker":0.4
         }
        }
    } 
    
    # best_field
    # 使用multi_match query简化写法如下:
    GET /your_index/your_type/_search
    {    
        "query": { 
           "multi_match":{
               "query":"关注 白日梦",
     					  # 指定检索的策略 best_fields(因为dis_max就是best field策略)
               "type":"best_fields",
      					# content^2 表示增加权重,相当于:boost2
               "fields":["name","content^2"],
    					 "tie_breaker":0.4,
    					 "minimum_should_match":3
           }
        }
    }
    
    # most_field
    GET /your_index/your_type/_search
    {    
        # most_fields策略、优先返回命中更多关键词的doc
        # 如下从title、name、content中搜索包含“赐我白日梦”的doc
        "query": { 
           "multi_match":{
               "query":"赐我白日梦",
     					  # 指定检索的策略most_fields
               "type":"most_fields",
               "fields":["title","name","content"]
           }
        }
    }
    

    针对跨越多个字段的检索除了上面的most_field和best_field之外,还可以使用copy_to预处理。

    这个copy_to实际上是在允许我们自定义一个_all字段, ES会将多个字段的值复制到一个_all中,然后再次检索时目标字段就使用我们通过copy_to创建出来的_all新字段中。

    示例:

    PUT my_index
    {
      "mappings": {
        "_doc": {
          "properties": {
            "first_name": {
              "type": "text",
              # 把当前的first_name copy进full_name字段中
              "copy_to": "full_name" 
            },
            "last_name": {
              "type": "text",
            	# 把当前的last_name copy进full_name字段中
              "copy_to": "full_name" 
            },
            "full_name": {
              "type": "text"
            }
          }
        }
      }
    }
    
    PUT my_index/_doc/1
    {
      "first_name": "John",
      "last_name": "Smith"
    }
    
    GET my_index/_search
    {
      "query": {
        "match": {
          "full_name": { 
            "query": "John Smith",
            "operator": "and"
          }
        }
      }
    }
    

    9.9、Arrays 和 Multi-field

    更多内容参见官网 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-types.html

    十、图解: master的选举、容错以及数据的恢复。

    https://img2018.cnblogs.com/blog/1496926/201911/1496926-20191104182531120-1172752624.png

    如上图为初始状态图

    假如图上的第一个节点是master节点,并且它挂掉了。那它挂掉后,整个cluster的status会变成red,表示存在数据丢失了集群不可用。然后集群会按照下面的步骤恢复:

    第一步:完成master的选举,自动在剩下的节点中选出一个节点当成master节点。

    第二步:选出master节点后,这个新的master节点会将P0在第三个节点中存在一个replica shard提升为primary shard,此时cluster 的 status = yellow,表示集群中的数据是可以被访问的但是存在部分replica shard不可用。

    第三步:重新启动因为故障宕机的node,并且将右边两个节点中的数据拷贝到第一个节点中,进行数据的恢复。

    十一、ES如何解决并发冲突

    ES内部的多线程异步并发修改时,通过_version版本号进行并发控制,每次创建一个document,它的_version内部版本号都是1,以后对这个doc的修改,删除都会使这个版本号增1。

    ES的内部需在Primary shard 和 replica shard之间同步数据,这就意味着多个修改请求其实是乱序的不一定按照先后顺序执行。

    相关语法:

    PUT /index/type/2?version=1{
        "name":"XXX"
    }
    

    上面的命令中URL中的存在?version=1,此时,如果存在其他客户端将id=2的这条记录修改过,导致id=2的版本号不等于1了,那么这条PUT语句将会失败并有相应的错误提示。这样也就规避了并发修改异常。


    拓展:

    ES也允许你使用自己的维护的版本号来进行并发控制,用法如下:

    PUT /index/type/2?version=1&version_type=external
    

    对比两者的不同:

    • 使用es提供的_version进行版本控制的话,需要你的PUT命令中提供的version == es的维护的version。

    • 添加参数version_type=external之后,假设当前ES中维护的doc版本号是1, 那么只有当用户提供的版本号大于1时,PUT才会成功。

    十二、路由原理

    什么是数据路由?

    一个index被分成了多个shard,文档被随机的存在某一个分片 上。客户端一个请求随机打向index中的一个分片,但是请求的doc可能不存在于这个分片上,于是接受请求的shard会将请求路由到真正存储数据的shard上,这个过程叫做数据路由。

    其中接受到客户端请求的节点称为coordinate node(协调节点),比如现在是客户端想修改服务端的一条消息,shard A接受到请求了,那么A就是 coordnate node协调节点。数据存储在B primary shard 上,那么协调节点就会将请求路由到B primary shard中,B处理完成后再向 B replica shard同步数据,数据同步完成后,B primary shard响应 coordinate node, 最后协调节点响应客户端结果。

    假如说你每个primary shard有多个存活的replica shard,默认情况下coordinate node会将请求使用round-robin的方式分散到replica shard和这个primary shard上(因为它们的数据是一样的)

    就像下图这样:

    节点对等的架构

    路由算法,揭开primary_shard数量不可变的面纱

    shard = hash(routing) % number_of_primary_shards
    

    公式不复杂,可以将上面的routing当成doc的id。无论是用户执行的还是自动生成的,反正肯定是唯一的。既然是唯一的,那每次hash得到的结果也是一样的, 这样一个唯一的值对主分片的数进行取余数,得到的结果就会在 0~最大分片数 之间。

    你看看上面的路由公式中后半部分使用的是 number_of_primary_shards ,这也是为什么ES规定,primary shard的数量不能改变,但是replica shard 可以改变的原因。

    除了上面说的路由方式,你还可以像下面这样定制路由规则:比如PUT /index/type/id?routing=user_id ,可以保证这类doc一定被路由到指定的shard上,而且后续进行应用级负载均衡时会批量提升读取的性能。

    像下面这种用法,可以保证你的doc一定被路由到一个shard上,

    # 添加一个doc,并制定routing
    PUT my_index/_doc/1?routing=user1&refresh=true 
    {
      "title": "This is a document"
    }
    
    # 通过id+routing获取你想要的doc
    GET my_index/_doc/1?routing=user1
    

    十三、写一致性及原理

    我们在发送任何一个增删改查时,都可以带上一个 consistency 参数,指明我们想要的写一致性是什么,如下

    PUT /index/type/id?consistency=quorum
    

    有哪些可选参数呢?

    • one:当我们进行写操作时,只要存在一个primary_shard=active 就能写入成功。
    • all:cluster中全部shard都为active时,可以写入成功。
    • quorum(法定的):也是ES的默认值, 要求大部分的replica_shard存活时系统才可用。

    quorum数量的计算公式: int((primary+number_of_replicas)/2)+1

    算一算,假如我们的集群中存在三个node,replica=1,那么cluster中就存在3+3*1=6个shard。

    int((3+1)/2)+1 = 3

    看计算的结果,只有当quorum=3 即replica_shard=3时,集群才是可用的。

    但是当我们的单机部署时,由于ES不允许同一个server的primary_shard和replica_shard共存,也就是说我们的replica数目为0,为什么ES依然可以用呢?这是因为ES提供了一种特殊的处理场景,也就是当number_of_replicas>1时,上述检查集群是否可用的机制才会生效。

    quorum不全时 集群进入wait()状态。 默认1分钟。在等待期间,期望活跃的shard的数量可以增加,到最后都没有满足这个数量的话就会timeout。

    我们在写入时也可以使用timeout参数, 比如: PUT /index/type/id?timeout=30通过自己设置超时时间来缩短超时时间默认的超时时间。

  • 相关阅读:
    逻辑代码题:五个学生参加计算机比赛
    逻辑代码题:如果昨天是明天,那今天就是周五了
    面试题:二叉树的遍历
    面试题:判断链表是否存在环
    面试题:将字符串中的中英文分开显示
    N皇后
    2017<java技术>预备作业2计科陈刚
    transition+transform合并效果案例
    transition CSS3动画属性之过渡
    Less 编译的几种方式
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/14382320.html
Copyright © 2011-2022 走看看