Elasticsearch document深度剖析

1. 针对Elasticsearch并发冲突问题,ES内部是如何解决的?

1)ES内部是线程异步并发修改的,是基于_version版本号进行乐观锁并发控制的;

2)若后修改的先到了,那么修改后版本发生变化,先修改的后到发现版本不一致就扔掉了,保证了数据的正确性;

3)primary shard与replica shard同步请求是多线程异步的;

2. 基于版本号的实际操作

1)PUT  /index/type/id?version=1;

es中的数据和客户端的数据的版本号必须是一致的,才能修改;

2)基于external version 进行了乐观锁并发控制;

不使用es内部的版本号,使用自己维护的版本号进行并发控制

?version=2&version.type=external

此方法保证只要version比ES中version大,就可以完成修改;

3. partial update

POST /index/type/id/_update

{

"doc":{

要修改的数据。

}

}

和全量替换相比优点:

1)所有的查询,修改和写回操作都是发生在es的内部,避免了所在的网络数据传输的开销,大大提升了性能;

2)减少了查询和修改的时间间隔,可以减少并发冲突的情况;

      partial update的实现原理:

                 和全量替换差不多:内部先获取document,将传过来的field更新到document的json中,将老的document标记为deleted,最后将修改后的新的document创建出来;

      partial update的并发控制原理:

                  内部自动执行并发乐观锁的并发控制策略。

                 POST /index/type/id/_update?retry_on_conflict=5 使用了重试策略,再次去新的版本号在更新;

4. 批量查询

优点:减少网络请求的开销

1) GET /_mget

GET /_mget
{
   "docs" : [
      {
         "_index" : "test_index",
         "_type" :  "test_type",
         "_id" :    1
      },
      {
         "_index" : "test_index",
         "_type" :  "test_type",
         "_id" :    2
      }
   ]
}

2) 如果查询的document是一个index下的不同type种的话

GET /test_index/_mget
{
   "docs" : [
      {
         "_type" :  "test_type",
         "_id" :    1
      },
      {
         "_type" :  "test_type",
         "_id" :    2
      }
   ]
}

3) 如果查询的数据都在同一个index下的同一个type下,最简单了

GET /test_index/test_type/_mget
{
   "ids": [1, 2]
}

5. 批量的增删改 bulk

POST /_bulk
{ "delete": { "_index": "test_index", "_type": "test_type", "_id": "3" }}
{ "create": { "_index": "test_index", "_type": "test_type", "_id": "12" }}
{ "test_field": "test12" }
{ "index": { "_index": "test_index", "_type": "test_type", "_id": "2" }}
{ "test_field": "replaced test2" }
{ "update": { "_index": "test_index", "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
{ "doc" : {"test_field2" : "bulk test1"} }

(1)delete:删除一个文档,只要1个json串就可以了
(2)create:PUT /index/type/id/_create,强制创建
(3)index:普通的put操作,可以是创建文档,也可以是全量替换文档
(4)update:执行的partial update操作

bulk api对json的语法,有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行;

bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志;

bulk size的最佳大小:

bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从1000~5000条数据开始,尝试逐渐增加。

6. document的数据路由

路由算法:shard = hash(routing) % number_of_primary_shards

routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的;

可以手动指定put /index/type/id?routing=user_id ;以保证说,某一类document一定被路由到一个shard上去,那么在后续进行应用级别的负载均衡,以及提升批量读取的性能的时候,是很有帮助的

这也是 primary shard数量不可变的谜底;

7. 增删改的内部原理

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都搞定之后,就返回响应结果给客户端;

8. 写一致性

增删改操作 put /index/type/id,都可以带上一个consistency参数 put /index/type/id?consistency=quorum

一致性策略:

one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

quorum机制,写之前必须确保大多数shard都可用,int( (primary + number_of_replicas) / 2 ) + 1,当number_of_replicas>1时才生效

quroum = int( (primary + number_of_replicas) / 2 ) + 1

如果节点数少于quorum数量,可能导致quorum不齐全,进而导致无法执行任何写操作,es提供了一种特殊的处理场景,就是说当number_of_replicas>1时才生效;

quorum不齐全时,会进行wait,默认1分钟,自己可以设置timeout的时间;

(注意:一个primary shard 的多个replica shard也不能在同一个node上)

9. document 查询的内部原理

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就都有了。

10._bulk api 的奇特JSON格式和性能优化的关系

如果采用比较良好的json数组格式,允许任意的换行,整个可读性非常棒,读起来很爽,es要按照下述流程去进行处理:

(1)将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是json文本,一份数据是JSONArray对象;
(2)解析json数组里的每个json,对每个请求中的document进行路由;
(3)为路由到同一个shard上的多个请求,创建一个请求数组;
(4)将这个请求数组序列化;
(5)将序列化后的请求数组发送到对应的节点上去;

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

奇特的格式:

{"action": {"meta"}}
{"data"}

(1)不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json;
(2)对每两个一组的json,读取meta,进行document路由;
(3)直接将对应的json发送到node上去;

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

原文地址:https://www.cnblogs.com/ntbww93/p/9806931.html