3.1 图解Es内部机制
3.1.1 图解Es分布式基础
1)es对复杂分布式机制的透明隐藏特性
-
分布式机制:分布式数据存储及共享。
-
分片机制:数据存储到哪个分片,副本数据写入。
-
集群发现机制:cluster discovery。新启动es实例,自动加入集群。
-
shard负载均衡:大量数据写入及查询,es会将数据平均分配。
-
shard副本:新增副本数,分片重分配。
2)Elasticsearch的垂直扩容与水平扩容
垂直扩容:使用更加强大的服务器替代老服务器。但单机存储及运算能力有上线。且成本直线上升。如10t服务器1万。单个10T服务器可能20万。
水平扩容:采购更多服务器,加入集群。大数据。
3)增减或减少节点时的数据rebalance
新增或减少es实例时,es集群会将数据重新分配。
4)master节点
功能:
-
创建删除节点
-
创建删除索引
5)节点对等的分布式架构
-
节点对等,每个节点都能接收所有的请求
-
自动请求路由
-
响应收集
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.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
}
}
查看分片信息
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.1.4 图解2个node环境下replica shard是如何分配的
1)replica shard分配:3个primary shard,3个replica shard,1 node 2)primary ---> replica同步 3)读请求:primary/replica
3.1.5 图解横向扩容
-
分片自动负载均衡,分片向空闲机器转移。
-
每个节点存储更少分片,系统资源给与每个分片的资源更多,整体集群性能提高。
-
扩容极限:节点数大于整体分片数,则必有空闲机器。
-
超出扩容极限时,可以增加副本数,如设置副本数为2,总共3*3=9个分片。9台机器同时运行,存储和搜索性能更强。容错性更好。
-
容错性:只要一个索引的所有主分片在,集群就就可以运行。
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.2 图解文档存储机制
3.2.1 数据路由
1) 文档存储如何路由到相应分片
一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。
2) 路由算法
# 哈希值对主分片数取模。
shard = hash(routing) % number_of_primary_shards
举例:
对一个文档经行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": []
}
场景:在程序中,架构师可以手动指定已有数据的一个属性为路由值,好处是可以定制一类文档数据存储到一个分片中。缺点是设计不好,会造成数据倾斜。
所以,不同文档尽量放到不同的索引中。剩下的事情交给es集群自己处理。
4) 主分片数量不可变
涉及到以往数据的查询搜索,所以一旦建立索引,主分片数不可变。
3.2.2 图解文档的增删改内部机制
增删改可以看做update,都是对数据的改动。一个改动请求发送到es集群,经历以下四个步骤:
(1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)
(2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
(3)实际的node上的primary shard处理请求,然后将数据同步到replica node。
(4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。
3.2.3 图解文档的查询内部机制
(1)客户端发送请求到任意一个node,成为coordinate node
(2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
(3)接收请求的node返回document给coordinate node
(4)coordinate node返回document给客户端
(5)特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了。
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对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能。