前言
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"}
数据刺进后版别号改变:
version和primary_term,每个文档都为初始值1.
而seq_no值各有改变。这是什么原因导致的呢?
检查数据分片分布
GET /user/_search
{
"explain": true,
"query": {
"match_all": {
}
}
}
数据分片分配关系如下:
- 分片0:5
- 分片1:2,3,4
- 分片2:1,6
从这里能够看出:
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.
修正前后比照:
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.
修正前后比照:
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.
修正前后比照:
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。
修正前后比照:
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。
修正前后比照:
primaryTerm自增测验
检查分片节点分配信息
GET /_cat/shards/user?v
分片 | 主分片节点 | 副本节点 |
---|---|---|
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
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 不变
修正前后比照:
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 不变
修正前后比照:
4.康复node-1
检查分片分配节点信息,康复成之前的分配了:
GET /_cat/shards/user?v
5.下线node-3
node3上有分片0和分片2的主分片。
检查分片分配节点信息:
GET /_cat/shards/user?v
分片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。
修正前后比照:
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主节点切换。
修正前后比照:
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不需求改变。
修正前后比照:
总结
为什么因毛病产生主分片切换时,需求记录primary_term了?
个人觉得如下场景会导致:
- 集群中某节点产生毛病宕机,节点上的主分片下线,可是毛病产生瞬间,主分片上的数据还未完全同步至其它副本。
- 集群中其它副本将被从头选举为新的主分片。
- 宕机节点从头上线,此刻它需求和新的主节点同步数据。
- 因为毛病时原主分片数据未完本同步,那么新老主分片的version和seq_no就可能重复存在冲突,那么怎么区分哪些数据是新主分片增加的了?这就是primary_term的作用了,在新主分片上产生的任何修正,primary_term的值都比老主分片产生的primary_term值+1.
seq_no记录分片上产生操作的顺序,配合primary_term一起解决分片数据同步的一致性问题。