Elasticsearch的原理

Elasticsearch分布式工作原理

前言

Elasticsearch 是分布式的,但是对于我们开发者来说并未过多的参与其中,我们只需启动对应数量的节点,并给它们分配相同的  cluster.name 让它们归属于同一个集群,创建索引的时候只需指定索引主分片数和 副分片数 即可,其他的都交给了ES 内部自己去实现

这和数据库的分布式和 同源的 solr 实现分布式都是有区别的,数据库要做集群分布式,比如分库分表需要我们指定路由规则和数据同步策略等,包括读写分离,主从同步等,solr的分布式也需依赖 zookeeper,但是 Elasticsearch 完全屏蔽了这些
虽然Elasticsearch 天生就是分布式的,并且在设计时屏蔽了了分布式的复杂性,但是我们还得知道它内部的原理

 

节点交互原理

Elasticsearch的原理_第1张图片

es和其他中间件一样,比如mysql,redis有master-slave模式。es集群也会选举一个节点做为master节点

master节点它的职责是维护全局集群状态,在节点加入或离开集群的时候重新分配分片

所有文档级别的写操作不会与master节点通信,master节点并不需要涉及到文档级别的变更和搜索等操作,es分布式不太像mysql的master-slave模式,mysql是写在主库,然后再同步数据到从库。而es文档写操作是分片上而不是节点上先写在主分片,主分片再同步给副分片,因为主分片可以分布在不同的节点上,所以当集群只有一个master节点的情况下,即使流量的增加它也不会成为瓶颈,就算它挂了,任何节点都有机会成为主节点

读写可以请求任意节点,节点再通过转发请求到目的节点,比如一个文档的新增,文档通过路由算法分配到某个主分片,然后找到对应的节点,将数据写入到主分片上,然后再同步到副分片上

 

写入文档

Elasticsearch的原理_第2张图片

1、客户端向node-1发送新增文档请求
2、节点通过文档的路由算法确定该文档属于主分片-P0。因为主分片-P0在node-3,所以请求会转发到node-3
3、文档在node-3的主分片-P0上新增,新增成功后,将请求转发到node-1和node-2对应的副分片-R0上。一旦所有的副分片都报告成功,node-3向node-1报告成功,node-1向客户端报告成功

 

读取文档

  1. 客户端向node-1发送读取文档请求
  2. 在处理读取请求时,node-1在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡

 

 Elasticsearch文档的路由原理

当新增一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?

 

路由算法

  • 首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的

shard = hash(routing) % number_of_primary_shards

routing 是一个可变值,默认是文档的  _id ,也可以设置成一个自定义的值。  routing通过  hash 函数生成一个数字,然后这个数字再除以  number_of_primary_shards (主分片的数量)后得到 余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置

这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了

  • 新增一个文档(指定id)

PUT /nba/_doc/1

{
    "name": "哈登",
    "team_name": "火箭",
    "position": "得分后卫",
    "play_year": "10",
    "jerse_no": "13"
}

查看文档在哪个分片上

GET /nba/_search_shards?routing=1

{
  "nodes" : {
    "naMWyY9TT6OPG56PmI7s1w" : {
      "name" : "ADMINISTRATOR",
      "ephemeral_id" : "j9EoNmTTQ4mTx-oqDeDiAw",
      "transport_address" : "127.0.0.1:9300",
      "attributes" : {
        "ml.machine_memory" : "16862834688",
        "xpack.installed" : "true",
        "ml.max_open_jobs" : "20"
      }
    }
  },
  "indices" : {
    "nba" : { }
  },
  "shards" : [
    [
      {
        "state" : "STARTED",
        "primary" : true,
        "node" : "naMWyY9TT6OPG56PmI7s1w",
        "relocating_node" : null,
        "shard" : 0,
        "index" : "nba",
        "allocation_id" : {
          "id" : "YFJzkvrJQZWAfqV5jm6M1g"
        }
      }
    ]
  ]
}

 

Elasticsearch的乐观锁

锁的简单分类

  • 悲观锁

顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁

  • 乐观锁

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,比如可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,因为我们elasticsearch一般业务场景都是写少读多,所以通过乐观锁可以在控制并发的情况下又能有效的提高系统吞吐量

 

版本号乐观锁

  • Elasticsearch 中对文档的 index , GET 和 delete 请求时,都会返回一个 _version,当文档被修改时版本号递增

 

  • 所有文档的更新或删除 API,都可以接受  version 参数,这允许你在代码中使用乐观的并发控制,这里要注意的是版本号要大于旧的版本号,并且加上version_type=external

 

  • 获取文档

GET /nba/_doc/1

{
    "_index": "nba",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "_seq_no": 4,
    "_primary_term": 7,
    "found": true,
    "_source": {
        "name": "哈登",
        "team_name": "⽕火箭",
        "position": "得分后卫",
        "play_year": "10",
        "jerse_no": "13"
    }
}

  • 通过版本号新增文档(version要大于旧的version)

POST /nba/_doc/1?version=2&version_type=external

{
    "name": "哈登",
    "team_name": "火箭",
    "position": "得分后卫",
    "play_year": "10",
    "jerse_no": "13"
}

 

Elasticsearch的分词原理

例子一

  • 我们创建一个文档

PUT test/_doc/1
{
    "msg":"乔丹是篮球之神"
}

  • 我们通过'乔丹'这个关键词来搜索这个文档

POST /test/_search

{
    "query": {
        "match": {
            "msg": "乔丹"
        }
    }
}

例子二

  • 试下使用中文分词器

PUT test/_mapping

{
    "properties": {
        "msg_chinese": {
            "type": "text",
            "analyzer": "ik_max_word"
        }
    }
}

POST test/_doc/1

{
    "msg": "乔丹是篮球之神",
    "msg_chinese": "乔丹是篮球之神"
}

POST /test/_search

{
    "query": {
        "match": {
            "msg_chinese": "乔"
        }
    }
}

POST /test/_search

{
    "query": {
        "match": {
            "msg": "乔"
        }
    }
}

写时分词

  • 我们使用来分析这个msg这个字段是怎样分词的

POST test/_analyze
{
    "field": "msg",
    "text": "乔丹是篮球之神"
}

乔,丹,是,篮,球,之,神

  • 再来分析这个msg_chinese这个字段是怎样分词的

POST test/_analyze
{
    "field": "msg_chinese",
    "text": "乔丹是篮球之神"
}

乔丹, 是, 篮球, 之神

  • 文档写入的时候会根据字段设置的分词器类型进行分词,如果不指定就是默认的standard分词器

 

  • 写时分词器需要在mapping中指定,而且一旦指定就不能再修改,若要修改必须重建索引

 

读时分词

由于读时分词器默认与写时分词器默认保持一致,拿上面的例子,你搜索 msg 字段,那么读时分词器为 Standard ,搜索 msg_chinese 时分词器则为 ik_max_word。这种默认设定也是非常容易理解的,读写采用一致的分词器,才能尽最大可能保证分词的结果是可以匹配的

  • 允许读时分词器单独设置

POST test/_search

{
    "query": {
        "match": {
            "msg_chinese": {
                "query": "乔丹",
                "analyzer": "standard"
            }
        }
    }
}

一般来讲不需要特别指定读时分词器,如果读的时候不单独设置分词器,那么读时分词器的验证方法与写时一致

 

深入分析

Elasticsearch的原理_第3张图片

分析器(analyzer)有三部分组成

char filter : 字符过滤器
tokenizer : 分词器
token filter :token过滤器

  • char filter(字符过滤器)

字符过滤器以字符流的形式接收原始文本,并可以通过添加、删除或更改字符来转换流。一个分析器可能有0个或多个字符过滤器

  • tokenizer (分词器)

一个分词器接收一个字符流,并将其拆分成单个token (通常是单个单词),并输出一个token流。比如使用whitespace分词器当遇到空格的时候会将文本拆分成token。"eating an apple" >> [eating, and, apple]。一个分析器必须只能有一个分词器

POST _analyze
{
    "text": "eating an apple",
    "analyzer": "whitespace"
}

token filter (token过滤器)

  • token过滤器接收token流,并且可能会添加、删除或更改tokens。比如一个lowercase token filter可以将所有的token转成小写。一个分析器可能有0个或多个token过滤器,它们按顺序应用

 

  • standard分析器
  • tokenizer

Stanard tokenizer

  • token filters

Standard Token Filter
Lower Case Token Filter

你可能感兴趣的:(elasticsearch)