3 ES内部机制

3.1 图解Es内部机制

3.1.1 图解Es分布式基础

1)es对复杂分布式机制的透明隐藏特性

  • 分布式机制:分布式数据存储及共享。

  • 分片机制:数据存储到哪个分片,副本数据写入。

  • 集群发现机制:cluster discovery。新启动es实例,自动加入集群。

  • shard负载均衡:大量数据写入及查询,es会将数据平均分配。

  • shard副本:新增副本数,分片重分配。

3 ES内部机制_服务器

2)Elasticsearch的垂直扩容与水平扩容

垂直扩容:使用更加强大的服务器替代老服务器。但单机存储及运算能力有上线。且成本直线上升。如10t服务器1万。单个10T服务器可能20万。

水平扩容:采购更多服务器,加入集群。大数据。

3 ES内部机制_数组_02

3)增减或减少节点时的数据rebalance

新增或减少es实例时,es集群会将数据重新分配。

3 ES内部机制_数组_03

4)master节点

功能:

  • 创建删除节点

  • 创建删除索引

3 ES内部机制_json_04

5)节点对等的分布式架构

  • 节点对等,每个节点都能接收所有的请求

  • 自动请求路由

  • 响应收集

3 ES内部机制_Elastic Stack 学习笔记_05

3.1.2 图解分片shard、副本replica机制

shard&replica机制

1)每个index包含一个或多个shard

2)每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力

3)增减节点时,shard会自动在nodes中负载均衡

4)primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard

5)replica shard是primary shard的副本,负责容错,以及承担读请求负载

6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改

7)primary shard的默认数量是1,replica默认是1,默认共有2个shard,1个primary shard,1个replica shard

注意:es7以前primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard

8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上

3 ES内部机制_服务器_06

3.1.3 图解单node环境下创建index是什么样子的

(1)单node环境下,创建一个index,有3个primary shard,3个replica shard (2)集群status是yellow (3)这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的 (4)集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求

PUT /test_index1
{
  "settings" : {
    "number_of_shards" : 3,
    "number_of_replicas" : 1
  }
}

3 ES内部机制_服务器_07

查看分片信息

GET /test_index1/_search_shards

返回:

{
"nodes" : {
  "iRrLtAAdRcGG7vKA2nsZhg" : {
    "name" : "node-1",
    "ephemeral_id" : "P9e0ZJNcSvyABTxt4DJsrg",
    "transport_address" : "127.0.0.1:9300",
    "attributes" : {
      "xpack.installed" : "true",
      "transform.node" : "true"
    },
    "roles" : [
      "data",
      "data_cold",
      "data_content",
      "data_frozen",
      "data_hot",
      "data_warm",
      "ingest",
      "master",
      "remote_cluster_client",
      "transform"
    ]
  }
},
"indices" : {
  "test_index1" : { }
},
"shards" : [
  [
    {
      "state" : "STARTED",
      "primary" : true,
      "node" : "iRrLtAAdRcGG7vKA2nsZhg",
      "relocating_node" : null,
      "shard" : 0,
      "index" : "test_index1",
      "allocation_id" : {
        "id" : "fWxXXz6gSoeqbTgw9yFuBw"
      }
    }
  ],
  [
    {
      "state" : "STARTED",
      "primary" : true,
      "node" : "iRrLtAAdRcGG7vKA2nsZhg",
      "relocating_node" : null,
      "shard" : 1,
      "index" : "test_index1",
      "allocation_id" : {
        "id" : "0DrWO-HETQCN4QiUtN7aLg"
      }
    }
  ],
  [
    {
      "state" : "STARTED",
      "primary" : true,
      "node" : "iRrLtAAdRcGG7vKA2nsZhg",
      "relocating_node" : null,
      "shard" : 2,
      "index" : "test_index1",
      "allocation_id" : {
        "id" : "B9O169hITQuzBkHcWeJpzA"
      }
    }
  ]
]
}

3 ES内部机制_json_08

3.1.4 图解2个node环境下replica shard是如何分配的

1)replica shard分配:3个primary shard,3个replica shard,1 node 2)primary ---> replica同步 3)读请求:primary/replica

3 ES内部机制_服务器_09

3.1.5 图解横向扩容

  • 分片自动负载均衡,分片向空闲机器转移。

  • 每个节点存储更少分片,系统资源给与每个分片的资源更多,整体集群性能提高。

  • 扩容极限:节点数大于整体分片数,则必有空闲机器。

  • 超出扩容极限时,可以增加副本数,如设置副本数为2,总共3*3=9个分片。9台机器同时运行,存储和搜索性能更强。容错性更好。

  • 容错性:只要一个索引的所有主分片在,集群就就可以运行。

3 ES内部机制_json_10

3.1.6 图解es容错机制 master选举,replica容错,数据恢复

以3分片,2副本数,3节点为例介绍。

  • master node宕机,自动master选举,集群为red

  • replica容错:新master将replica提升为primary shard,yellow

  • 重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green

3 ES内部机制_数组_11

3.2 图解文档存储机制

3.2.1 数据路由

1) 文档存储如何路由到相应分片

3 ES内部机制_Elastic Stack 学习笔记_12

一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。

2) 路由算法

# 哈希值对主分片数取模。
shard = hash(routing) % number_of_primary_shards

3 ES内部机制_数据_13

举例:

对一个文档经行crud时,都会带一个路由值 routing number。默认为文档_id(可能是手动指定,也可能是自动生成)。

存储1号文档,经过哈希计算,哈希值为2,此索引有3个主分片,那么计算2%3=2,就算出此文档在P2分片上。

决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的

无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

3) 手动指定 routing number

PUT /test_index/_doc/15?routing=num
{
"num": 0,
"tags": []
}

场景:在程序中,架构师可以手动指定已有数据的一个属性为路由值,好处是可以定制一类文档数据存储到一个分片中。缺点是设计不好,会造成数据倾斜。

3 ES内部机制_数据_14

所以,不同文档尽量放到不同的索引中。剩下的事情交给es集群自己处理。

4) 主分片数量不可变

涉及到以往数据的查询搜索,所以一旦建立索引,主分片数不可变。

3 ES内部机制_json_15

3.2.2 图解文档的增删改内部机制

增删改可以看做update,都是对数据的改动。一个改动请求发送到es集群,经历以下四个步骤:

(1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)

3 ES内部机制_数组_16

 

(2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)

3 ES内部机制_Elastic Stack 学习笔记_17

 

(3)实际的node上的primary shard处理请求,然后将数据同步到replica node。

3 ES内部机制_服务器_18

 

(4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。

3.2.3 图解文档的查询内部机制

(1)客户端发送请求到任意一个node,成为coordinate node

3 ES内部机制_数据_19

 

(2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡

3 ES内部机制_数据_20

 

(3)接收请求的node返回document给coordinate node

3 ES内部机制_Elastic Stack 学习笔记_21

(4)coordinate node返回document给客户端

3 ES内部机制_数组_22

 

 

(5)特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了。

3 ES内部机制_json_23

3.2.4 bulk api奇特的json格式

POST /_bulk
{"action": {"meta"}}\n
{"data"}\n
{"action": {"meta"}}\n
{"data"}\n

[
  {
       "action":{
           "method":"create"
      },
       "data":{
           "id":1,
           "field1":"java",
           "field1":"spring",
      }
  },
    {
       "action":{
           "method":"create"
      },
       "data":{
           "id":2,
           "field1":"java",
           "field1":"spring",
      }
  }
]

1、bulk中的每个操作都可能要转发到不同的node的shard去执行

2、如果采用比较良好的json数组格式

允许任意的换行,整个可读性非常棒,读起来很爽,es拿到那种标准格式的json串以后,要按照下述流程去进行处理

(1)将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是json文本,一份数据是JSONArray对象

(2)解析json数组里的每个json,对每个请求中的document进行路由

(3)为路由到同一个shard上的多个请求,创建一个请求数组。100请求中有10个是到P1.

(4)将这个请求数组序列化

(5)将序列化后的请求数组发送到对应的节点上去

3、耗费更多内存,更多的jvm gc开销

之前提到过bulk size最佳大小的那个问题,一般建议说在几千条那样,然后大小在10MB左右,所以说,可怕的事情来了。假设说现在100个bulk请求发送到了一个节点上去,然后每个请求是10MB,100个请求,就是1000MB = 1GB,然后每个请求的json都copy一份为jsonarray对象,此时内存中的占用就会翻倍,就会占用2GB的内存,甚至还不止。因为弄成jsonarray之后,还可能会多搞一些其他的数据结构,2GB+的内存占用。

占用更多的内存可能就会积压其他请求的内存使用量,比如说最重要的搜索请求,分析请求,等等,此时就可能会导致其他请求的性能急速下降。

另外的话,占用内存更多,就会导致java虚拟机的垃圾回收次数更多,跟频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致es的java虚拟机停止工作线程的时间更多。

4、现在的奇特格式

POST /_bulk
{ "delete": { "_index": "test_index", "_id": "5" }} \n
{ "create": { "_index": "test_index", "_id": "14" }}\n
{ "test_field": "test14" }\n
{ "update": { "_index": "test_index", "_id": "2"} }\n
{ "doc" : {"test_field" : "bulk test"} }\n

(1)不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json

(2)对每两个一组的json,读取meta,进行document路由

(3)直接将对应的json发送到node上去

5、最大的优势在于,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能。

 

本文摘抄或总结其他笔记,笔记不涉及任何商业用途,如果侵权请及时联系处理