zoukankan      html  css  js  c++  java
  • Elasticsearch内容汇总[持续更新]

    一、Elasticsearch技术简介

    Elastic本身也是一个分布式存储系统,如同其他分布式系统一样,我们经常关注的一些特性如下。

    • 数据可靠性:通过分片副本和事务日志机制保障数据安全
    • 服务可用性:在可用性和一致性的取舍方面,默认情况下Elastic更倾向于可用性,只要主分片可用即可执行写入操作
    • 一致性:弱一致性。只要主分片写成功,数据就可能被读取。因此读取操作在主分片和副本分片上可能会得到不同的结果
    • 原子性:索引的读写、别名更新是原子操作,不会出现中间状态。但Bulk不是原子操作。不能用来实现事务
    • 扩展性:主副本分片都可以承担读请求,分担系统负载

    1.1 Elasticsearch与MySQL的关系

    RDBMS Elasticsearch
    Table Index(Type)
    Row Document
    Column Field
    Schema Mapping
    SQL DSL

    1.1.1 Mapping

    索引结构

    1.1.2 DSL

    查询语句

    1.1.3 倒排索引

    正排索引: 文档ID -> 文档内容
    倒排索引:文档内容 -> 文档ID

    文档ID 文档内容
    1 Mastering Elasticsearch
    2 Elasticsearch Server
    3 Elasticsearch Essentials
    Term Count DocumentId:Position
    Elasticsearch 3 1:1,2:0,3:0
    Mastering 1 1:0
    Server 1 2:1
    Essentials 1 3:1

    倒排索引的核心组成

    • 倒排索引包含两个部分
      • 单词词典(Term Dictionary),记录所有文档的单词,记录单词到倒排列表的关联关系
        • 单词词典一般比较大,可以通过B+树或哈希拉链法实现,以满足高性能的插入与查询
      • 倒排列表(Posting List)记录了单词对应的文档结合,由倒排索引项组成
        • 倒排索引项(Posting)
          • 文档ID
          • 词频TF - 该单词在文档中出现的次数,用于相关性评分
          • 位置(Position)- 单词在文档中分词的位置。用于语句搜索(phrase query)
          • 便宜(Offset)- 记录单词的开始结束位置,实现高亮显示
    文档ID 文档内容
    1 Mastering Elasticsearch
    2 Elasticsearch Server
    3 Elasticsearch Essentials

    Posting List

    DocId TF Position Offset
    1 1 1 <10,23>
    2 1 0 <0,13>
    3 1 0 <0,13>
    • Elasticsearch的JSON文档中的每个字段,都有自己的倒排索引
    • 可以指定对某些字段不做索引
      • 优点:节省存储空间
      • 缺点:字段无法被搜索

    1.1.4 Lucene字典数据结构FST

    常见的词典数据结构:

    名称 特点
    排序列表Array/List 使用二分法查找,不平衡
    HashMap/TreeMap 性能高,内存消耗大,几乎是原始数组的三倍
    Skip List 跳跃表,可快速查找词语,在Lucene、Redis、HBase等均有实现。相对于TreeMap等结构,特别适合高并发场景
    Trie 适合英文词典,如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的trie树将非常消耗内存
    Double Array Trie 适合做中文词典,内存占用小,很多分词工具均采用此算法
    Ternary Search Tree 三叉树,每一个node有3个节点,兼具省空间和查询快的优点
    Finite State Transducers(FST) 一种有限状态机,Luncene 4有开源实现,并大量使用

    FST数据结构:
    插入单词“cat”、”deep“、”do“、”dog“、”dogs“

    1.2 Elasticsearch基本概念

    高可用高扩展的分布式搜索引擎——Elasticsearch

    1.2.1 节点

    节点即一个Elasticsearch的实例,本质上就是一个Java进程,一台机器上可以运行多个Elasticsearch进程,但是生产环境一般建议一台机器上只运行一个Elasticsearch实例

    Master-eligible节点和Master节点

    • 每个节点启动后默认就是一个Master-eligible节点,该类型节点可以参加选主流程,成为Master节点。
    • 当第一个节点启动的时候,它会将自己选举成Master节点
    • 每个节点都保存了集群的状态,但是只有Master节点可以修改集群的状态信息(如所有节点的信息、所有的索引以及其相关的Mapping、Setting信息、分片路由信息等)

    Data节点和Coordinating节点

    • Data节点

      • 可以保存数据的节点,叫做Data Node。负责保存分片数据。在数据扩展上起到了至关重要的作用。
    • Coordinating节点

      • 负责接收Client的请求,将请求分发到合适的节点,最终把结果汇集到一起
      • 每个节点默认都起到了Coordinating Node的职责
    • Ingest 节点

      • 数据前置处理转换节点,支持pipeline管道设置
      • 可以使用ingest节点对数据进行过滤、转换等操作
      • 每个节点默认都起到了该职责,即在文档进入索引前做预处理

    Hot节点和Warm节点

    不同硬件配置的Data Node,用来实现Hot & Warm 架构,降低集群部署成本

    分片

    又称为主分片,用以解决数据水平扩展问题。通过主分片,可以将数据分布到集群内的所有节点之上。

    • 一个分片是一个运行Lucene的实例
    • 主分片数在索引创建时指定,后续不允许修改,除非Reindex

    副本

    用以解决数据高可用问题,是主分片的拷贝。

    • 副本分片数可以动态调整
    • 增加副本数,可以在一定程度上提高服务的可用性(读取的吞吐)

    1.2.2 水平扩展

    1.2.3 写入流程

    write
    (1)客户端向NODE1发送写请求。
    (2)NODE1使用文档ID来确定文档属于分片0,通过集群状态中的内容路由表信息获知分片0的主分片位于NODE3,因此请求被转发到NODE3上。
    (3)NODE3上的主分片执行写操作。如果写入成功,则它将请求并行转发到 NODE1和NODE2的副分片上,等待返回结果。当所有的副分片都报告成功,NODE3将向协调节点报告成功,协调节点再向客户端报告成功。

    在客户端收到成功响应时,意味着写操作已经在主分片和所有副分片都执行完成。
    写入底层原理
    官网translog配置说明

    1.2.4 查询流程

    get流程

    (1)客户端向NODE1发送读请求。
    (2)NODE1使用文档ID来确定文档属于分片0,通过集群状态中的内容路由表信息获知分片0有三个副本数据,位于所有的三个节点中,此时它可以将请求发送到任意节点,这里它将请求转发到NODE2。
    (3)NODE2将文档返回给 NODE1,NODE1将文档返回给客户端。

    1.2.5 搜索流程

    search
    (1)客户端发送search请求到NODE 3。
    (2)NODE 3将查询请求转发到索引的每个主分片或副分片中。
    (3)每个分片在本地执行查询,并使用本地的Term/Document Frequency信息进行打分,添加结果到大小为from + size的本地有序优先队列中。
    (4)每个分片返回各自优先队列中所有文档的ID和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表。

    1.2.6 动态索引

    动态索引

    • 新增加字段
      • Dynamic 设置为true时,一旦有新增字段的文档写入,Mapping也同时被更新
      • Dynamic 设置为false,Mapping不会被更新,新增字段的数据无法被索引,但是信息会出现在_source中
      • Dynamic 设置为Strict,文档写入失败
    • 对已有字段,一旦已经有数据写入,就不再支持修改字段定义
      • Lucene实现的倒排索引,一旦生成后,就不允许修改
    • 如果希望改变字段类型,使用Reindex API,重建索引
      • 因为如果修改了字段的数据类型,会导致已被索引的数据无法被搜索
      • 如果是增加新的字段,就不会有这样的影响

    1.2.7 数据建模

    1.2.7.1 Elasticsearch中处理关联关系

    • 对象类型
    • 嵌套对象(Nested Object)
    • 父子关联关系(Parent / Child)
    • 应用端关联
    对象类型

    因为Elasticsearch会把JSON打平(扁平式键值对结构),所以能够搜索到名称为”John“,年龄为31的文档,因为这些数据都能够被搜索到。(对象之间没有界限)
    Object关联关系

    Nested Data Type
    • Nested数据类型:允许对象数组中的对象被独立索引
    • 使用nested和properties关键字,将所有actors索引到多个分隔的文档
    • 在内部,Nested文档会被保存在两个Lucene文档中,在查询时做Join处理

    如下对对象设置了“nested”类型,则不再能够搜索到”不正确的数据”了。
    Nested

    父子关联关系
    • 对象和Nested对象的局限性
      • 每次更新,需要重新索引整个对象(包括根对象和嵌套对象)
    • ES提供了类似关系型数据库中Join的实现。使用Join数据类型实现,可以通过维护Parent/Child的关系,从而分离两个对象
      • 父文档和子文档是两个独立的文档
      • 更新父文档无需重新索引子文档。子文档被添加,更新或者删除也不会影响到父文档和其他的子文档
      • 父文档和子文档必须在相同的分片上 -> 确保查询join的性能
      • 当指定子文档的时候,必须指定它的父文档Id -> 使用route参数保证分配到相同分片上

    父子文档

    Nested Object Parent / Child
    优点 文档存储在一起,读取性能高 父子文档可以独立更新
    缺点 更新嵌套的子文档时,需要更新整个文档 需要额外的内存维护关系。读取性能相对差
    适用场景 子文档偶尔更新,以查询为主 子文档更新频繁

    2.4 优化手段

    2.4.1 深度分页

    2.4.1.1 FROM+SIZE

    这种分页方式,当FROM+SIZE > 10000的时候,Elasticsearch会报错,因为这里它有个默认分页窗口设置(当然也可以修改,一般不建议修改)。

    • ES天生就是分布式的。查询信息的时候需要从多个分片(多台机器)上拉取数据,并且ES天生就需要满足排序的需要(按照相关性算分)
    • 当一个查询: From = 990, Size = 10
      • 会在每个分片上都获取1000个文档。然后,通过Coordinating Node聚合所有结果。最后再通过排序选取前1000个文档
      • 页数越深,占用内存越多。为了避免深度分页带来的内存开销。ES有一个设定,默认限定到10000个文档。
        • Index.max.result.window

    特别注意:如果你的查询没有指定from,size的话ES默认会限制为from,size=0,10。
    提示:from是指偏移量,不是第几页,与MySQL的limit后的两个参数一样。(我就脑瓜子疼了很久,刚开始一直把from当页码。。)

    2.4.1.2 SearchAfter

    • 避免深度分页的性能问题,可以实时获取下一页文档信息
      • 不支持指定偏移量
      • 只能继续向后偏移翻页
    • 第一步搜索需要指定sort,并且保证值是唯一的(可以通过加入_id保证唯一性)
    • 然后使用上一次查询的结果集中,最后一个文档的sort值继续进行查询
      SearchAfter
      关键点:根据提供的排序属性排序后的sort值为依据,向后继续翻页。类似于MySQL中,LIMIT 10000,30。我拿到了第9999条数据的id值,然后SELECT * FROM a WHERE id > 9999 LIMIT 30。
      特别注意:SearchAfter这种特性,很显然不支持跳页,但是它也是能够实时向后翻页的,而接下来介绍的Scoll翻页方式就不支持实时。

    2.4.1.3 ScollAPI

    • 创建一个快照,有新的数据写入以后,无法被查到
    • 每次查询后,输入上一次的Scoll Id

    ScollAPI
    我理解和SearchAfter类似,一个是通过传递上一次的排序值,一个是通过传递上一次的Scoll值。不同的是,SearchAfter是实时的,而Scoll方式对翻页过程中有数据变更是无感知的。

    分页总结

    一般不建议深度分页,尽可能让业务增加时间范围,减少搜索范围,或者说直接使用另外两种,滚动分页即可。需要注意的是后两者对数据变化的感知是不一样的,具体需要根据场景来选择分页方式。
    思考:Scoll是快照,即那一瞬间的快照,所以翻页是在快照中自己玩,对数据的变化无感知了,那么如果数据量很大,会不会把内存玩脱。

    代码片段

    PUT nested_index
    {
      "mappings": {
        "properties": {
          "actors" : {
            "type" : "nested",
            "properties": {
              "first_name" : {"type" : "keyword"},
              "last_name" : {"type" : "keyword"}
            }
          },
          "title": {
            "type" : "text",
            "fields" : {"keyword": {"type" : "keyword","ignore_above":256}}
          }
        }
      }
    }
    PUT nested_index/_doc/1
    {
      "title": "Speed",
      "actors": [
        {"first_name": "Keanu","last_name": "Reeves"},
        {"first_name": "Dennis","last_name": "Hopper"}
      ]
    }
    POST nested_index/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "title": "Speed"
              }
            },
            {
              "nested": {
                "path": "actors",
                "query": {
                  "bool": {
                    "must": [
                      {"match": {"actors.first_name": "Keanu"}},
                      {"match": {"actors.last_name": "Hopper"}}
                    ]
                  }
                }
              }
            }
          ]
        }
      }
    }
    
    
    #指定父子关系,父亲为博客“blog”,子为评论“comment”
    PUT parent_child_index
    {
      "mappings": {
        "properties": {
          "blog_comments_relation": {
            "type" : "join",
            "relations": { "blog": "comment"}
          },
          "content": {"type":"text"},
          "title":{"type": "keyword"}
        }
      }
    }
    #索引父文档,确认自身身份为“blog”->父文档
    PUT parent_child_index/_doc/blog1
    {
      "title" : "Learning Elasticsearch",
      "content": "hello Elasticsearch",
      "blog_comments_relation": {"name":"blog"}
    }
    #索引子文档,引用父文档。
    PUT parent_child_index/_doc/comment1?routing=blog1
    {
      "comment": "I am learning ELK",
      "username": "Jack",
      "blog_comments_relation": {"name":"comment","parent":"blog1"}
    }
    
    PUT parent_child_index/_doc/blog2
    {
      "title" : "Learning Elasticsearch",
      "content": "hello Elasticsearch",
      "blog_comments_relation": {"name":"blog"}
    }
    PUT parent_child_index/_doc/comment2?routing=blog2
    {
      "comment": "I am learning ELK too",
      "username": "Bob",
      "blog_comments_relation": {"name":"comment","parent":"blog2"}
    }
    
    #查询所有文档
    POST parent_child_index/_search
    {}
    
    #根据Parent Id查询
    POST parent_child_index/_search
    {
      "query": {
        "parent_id":{
          "type": "comment",
          "id": "blog2"
        }
      }
    }
    # Has Child查询,返回父文档
    POST parent_child_index/_search
    {
      "query": {
        "has_child": {
          "type": "comment",
          "query": {
            "match": {
              "username": "Jack"
            }
          }
        }
      }
    }
    # Has Parent查询,返回相关子文档
    POST parent_child_index/_search
    {
      "query": {
        "has_parent": {
          "parent_type": "blog",
          "query": {
            "match": {
              "title": "Learning Elasticsearch"
            }
          }
        }
      }
    }
    
  • 相关阅读:
    [saiku] 系统登录成功后查询Cubes
    216. Combination Sum III
    215. Kth Largest Element in an Array
    214. Shortest Palindrome
    213. House Robber II
    212. Word Search II
    211. Add and Search Word
    210. Course Schedule II
    分硬币问题
    开始学习Python
  • 原文地址:https://www.cnblogs.com/deepSleeping/p/14984771.html
Copyright © 2011-2022 走看看