前言

elasticsearch运用version、seqNo、primaryTerm三个字段做乐观锁并发操控。

早期版别6.7曾经,运用version来操控文档版别,如修正指定版别:

PUT user/_doc/1?version=11

6.7及以后版别运用seqNo和primaryTerm,修正指定版别运用下面的方式:

PUT user/_doc/1?if_seq_no=22&if_primary_term=2

version字段的机制仍然是保存的,在8.6的源码中,对于版别冲突的校验,仍然包含了对version的验证。

这三个字段会在不同的场景逻辑下实现自增长。

  • version字段针对每个文档的修正(包含删去)操作。
  • seq_no字段针对每个分片的文档修正(包含删去)操作。
  • primary_term字段针对毛病导致的主分片重启或主分片切换,每产生一次自增1。

下面来测验验证一下这个结论。

环境

  • elasticsearch集群:node-1,node-2,node-3
  • 索引:user,3个主分片,每个分片一个副本。

创建索引

PUT user
{
  "settings" : {
    "index" : {
      "number_of_shards" : 3,
      "number_of_replicas" : 1
    }
  }
}

version和seqNo自增测验

批量刺进数据

POST user/_bulk
{"index":{"_id":"1"}}
{"name":"001"}
{"index":{"_id":"2"}}
{"name":"002"}
{"index":{"_id":"3"}}
{"name":"003"}
{"index":{"_id":"4"}}
{"name":"004"}
{"index":{"_id":"5"}}
{"name":"005"}
{"index":{"_id":"6"}}
{"name":"006"}

数据刺进后版别号改变:

Elasticsearch中version与seqNo+primaryTerm分析

version和primary_term,每个文档都为初始值1.

而seq_no值各有改变。这是什么原因导致的呢?

检查数据分片分布

GET /user/_search
{
  "explain": true,
  "query": {
    "match_all": {
    }
  }
}

Elasticsearch中version与seqNo+primaryTerm分析

数据分片分配关系如下:

  1. 分片0:5
  2. 分片1:2,3,4
  3. 分片2:1,6

Elasticsearch中version与seqNo+primaryTerm分析

从这里能够看出:

id=2,id=3和id=4的文档同属分片1,所以seq_no从0 -> 1 -> 2。

id=1和id=6的文档同属分片2,所以seq_no从0 -> 1。

seq_no的初始值从0开端。

开端测验

1.修正id=2的文档

POST user/_doc/2
{
  "name":"002"
}
{
  "_index": "user",
  "_id": "2",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}
  • version 1 -> 2
  • seq_no 0 -> 3
  • primary_term 不变

id=2的文档和id=3,id=4两个文档在同属分片1,分片1的seq_no当时值为2。故seq_no增1,值为3.

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

2.修正id=1的文档

POST user/_doc/1
{
  "name":"001"
}
{
  "_index": "user",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 1
}
  • version 1 -> 2
  • seq_no 0 -> 2
  • primary_term 不变

id=1的文档和id=6的文档在同属分片2,分片2的seq_no当时最大值为1,所以seq_no变成2.

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

3.删去id=3的文档

DELETE /user/_doc/3
{
  "_index": "user",
  "_id": "3",
  "_version": 2,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}
  • version 1 -> 2
  • seq_no 1 -> 4
  • primary_term 不变

id=3的文档归属分片1,分片1的seq_no当时值为3。故seq_no最新值为4.

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

4.修正id=2的文档

POST user/_doc/2
{
  "name":"002"
}
{
  "_index": "user",
  "_id": "2",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 5,
  "_primary_term": 1
}
  • version 2 -> 3
  • seq_no 3 -> 5
  • primary_term 不变

id=3的文档归属分片1,分片1的seq_no当时值为4。故seq_no为5。

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

5.修正id=1的文档

POST user/_doc/1
{
  "name":"001"
}
{
  "_index": "user",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}
  • version 2 -> 3
  • seq_no 2 -> 3
  • primary_term 不变

id=1的文档归属分片2,分片2的seq_no当时值为2。

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

primaryTerm自增测验

检查分片节点分配信息

GET /_cat/shards/user?v

Elasticsearch中version与seqNo+primaryTerm分析

分片 主分片节点 副本节点
0 node-3 node-1
1 node-2 node-1
2 node-3 node-1

开端测验

1.下线node-1

node-1节点上均为副本节点,所以不影响primaryTerm。

下线node-1后检查分片分配节点信息:

GET /_cat/shards/user?v

Elasticsearch中version与seqNo+primaryTerm分析

2.修正id=1的文档

POST user/_doc/1
{
  "name":"001"
}
{
  "_index": "user",
  "_id": "1",
  "_version": 4,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}
  • version 3 -> 4
  • seq_no 3 -> 4
  • primary_term 不变

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

3.修正id=5的文档

POST user/_doc/5
{
  "name":"005"
}
{
  "_index": "user",
  "_id": "5",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}
  • version 1 -> 2
  • seq_no 0 -> 1
  • primary_term 不变

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

4.康复node-1

检查分片分配节点信息,康复成之前的分配了:

GET /_cat/shards/user?v

Elasticsearch中version与seqNo+primaryTerm分析

5.下线node-3

node3上有分片0和分片2的主分片。

检查分片分配节点信息:

GET /_cat/shards/user?v

Elasticsearch中version与seqNo+primaryTerm分析

分片0和分片2的主分片转移至node-1节点了。

6.修正id=5的文档

POST user/_doc/5
{
  "name":"005"
}
{
  "_index": "user",
  "_id": "5",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 2
}
  • version 2 -> 3
  • seq_no 1 -> 2
  • primary_term 1 -> 2

id=5归属分片0.node3下线分片0的主节点切换至node-1。

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

7.修正id=2的文档

POST user/_doc/2
{
  "name":"002"
}
{
  "_index": "user",
  "_id": "2",
  "_version": 4,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 6,
  "_primary_term": 1
}
  • version 3 -> 4
  • seq_no 4 -> 6
  • primary_term 1 -> 1 不变

id=2归属分片1.node3下线没有导致分片1主节点切换。

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

8.康复node-3

检查分片分配节点信息:

GET /_cat/shards/user?v
index shard prirep state   docs  store ip        node
user  0     p      STARTED    1  4.6kb 127.0.0.1 node-1
user  0     r      STARTED    1  4.6kb 127.0.0.1 node-2
user  1     p      STARTED    2  8.9kb 127.0.0.1 node-3
user  1     r      STARTED    2  8.9kb 127.0.0.1 node-1
user  2     r      STARTED    2 11.1kb 127.0.0.1 node-3
user  2     p      STARTED    2 11.1kb 127.0.0.1 node-1

分片1的主分片从node-2切换至node-3了,这是由eslasticsearch内部分配机制调配的。

9.修正id=2的文档

POST user/_doc/2
{
  "name":"002"
}
{
  "_index": "user",
  "_id": "2",
  "_version": 5,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 7,
  "_primary_term": 1
}
  • version 3 -> 4
  • seq_no 4 -> 6
  • primary_term 1 -> 1 不变

此处primary_term并没有产生改变,因为分片1的主分片虽然切换了,可是这个切换不是毛病直接导致的,而是由eslasticsearch内部机制分配的,es内部机制分配能够保障切换过程中的一致性,故此刻primary_term不需求改变。

修正前后比照:

Elasticsearch中version与seqNo+primaryTerm分析

总结

为什么因毛病产生主分片切换时,需求记录primary_term了?

个人觉得如下场景会导致:

  1. 集群中某节点产生毛病宕机,节点上的主分片下线,可是毛病产生瞬间,主分片上的数据还未完全同步至其它副本。
  2. 集群中其它副本将被从头选举为新的主分片。
  3. 宕机节点从头上线,此刻它需求和新的主节点同步数据。
  4. 因为毛病时原主分片数据未完本同步,那么新老主分片的version和seq_no就可能重复存在冲突,那么怎么区分哪些数据是新主分片增加的了?这就是primary_term的作用了,在新主分片上产生的任何修正,primary_term的值都比老主分片产生的primary_term值+1.

seq_no记录分片上产生操作的顺序,配合primary_term一起解决分片数据同步的一致性问题。