Elasticsearch的数据复制机制

当往ES写入一条document时,首先经过计算得到shard id,决定写往哪一个Primary shard

写入primary shard后,并发往replica shard同步数据:

image-20220709093053244

但这种方式存在问题,成功写入primary shard后,在往其他replica同步的过程中,如果primary shard挂掉了,这个时候就会出现数据不一致的问题:

image-20220709093312563

为了解决这个问题,最主要的是需要记录每次数据操作顺序情况,同时需要考虑到在各种异常场景下(服务器重启、网络异常、硬件故障、Java长时间GC、软件升级等),仍然能够正常地、一致地记录、恢复这些操作到一致点。由此Elasticsearch引入了以下设计:

  • Sequence Number:在数据的主分片上会分配一个唯一的、递增的序号,用于记录数据更新的情况,每次数据更新后都会自动增加,保证新的数据更新一定会比旧的数据更新获得更大的Sequence Number
  • Primary Term:在数据的主分片上分配一个递增的序号,用于记录数据主分片是否发生切换,当主分片发生切换时会自动增加,保证拥有较大term的分片一定是较新的主分片。每个分片最新的term计数存储在cluster state元数据中。该设计类似于Raft中的term、Zab中的epoch、Viewstamped Replication中的view-number

但是对于数据量大的index,上面的sequence numberprimary term是比较耗时的

于是Elasticsearch又引入了以下机制:

  • Local checkpoint:在节点本地已完成数据处理的最低Sequence Number,然后取最大的一个值,确保在本地节点上所有低于该值的数据操作都是已经全部完成了的
  • Global checkpoint:在所有的主副本分片上都已经完成数据处理的最大Sequence Number,确保在数据所有的主副本分片上低于该值的数据操作都是已经完成了的。该数据根据各个数据副本所反馈的Local checkpoint在主分片上生成,并随下一次数据复制同步到各数据副本

下面通过一个动画来具体说明该设计是如何应用的:

img

    • 在term 1,主分片上发生了数据更新seq 0
    • 本地的更新操作完成后被复制到副本1和副本2
    • 主分片上的local checkpoint更新至seq 0
    • 同时副本1和副本2的local checkpoint也更新至seq 0,并向主分片反馈local checkpoint的更新情况(seq 0)
    • 在term 1,主分片上发生了数据更新seq 1
    • 本地的更新操作完成后被复制到副本1和副本2,同时将global checkpoint更新至seq 0,该信息随数据复制操作被更新到副本1和副本2
    • 各副本的local checkpoint更新至seq 1,并向主分片反馈local checkpoint的更新情况(seq 1)
    • 在term 1,主分片上发生了数据更新seq 2和3
    • 本地的更新操作完成后被复制到副本1和副本2,同时将global checkpoint更新至seq 1,该信息随数据复制操作被更新到副本1和副本2
    • 但副本1和副本2接受到的数据顺序有差异:副本1先接收到seq 2,local checkpoint更新至seq 2;副本2先接收到seq 3,由于seq 2未处理,因此local checkpoint暂未更新
    • 后续副本1接收到seq 3,副本2接收到seq2,则local checkpoint都更新到seq 3,并向主分片反馈local checkpoint的更新情况(seq 3)
    • 在term 1,主分片上发生了数据更新seq 4、5和6
    • 本地的更新操作完成后被复制到副本1和副本2,同时将global checkpoint更新至seq 3,该信息随数据复制操作被更新到副本1和副本2
    • 主分片的local checkpoint更新至seq 6 seq 5和6被复制到了副本1,local checkpoint暂未更新;seq 4和6被复制到了副本2,local checkpoint更新至seq 4
    • 这时主分片发生了异常,导致主分片切换到原副本1,同时term更新为2。
    • 在term 2,新的主分片local checkpoint更新至seq 6
    • 新的主分片将数据更新seq 5和6复制到了原副本2,同时覆盖掉了原副本2上的数据更新seq 4
    • 副本分片更新local checkpoint至seq 6,并向主分片反馈local checkpoint的更新情况(seq 6)
    • 在后续的数据更新中global checkpoint也更新至seq 6
    • 这时即使原主分片恢复正常,但由于其拥有的term较低,其他的数据分片不会接受其发出的数据写入请求,不会发生旧数据覆盖新数据的问题
    • 同时其发现有比自身term更高的主分片出现后,会自动转换自身角色为副本分片,接受新主分片的数据同步请求
    • 从新的主分片同步自异常前最后一个global checkpoint以来的数据变化,完成分片恢复过程,最终所有数据副本重新达到一致状态

采用上述设计方案,当发生数据分片由于故障需要恢复的时候,不再完全需要进行全量文件的复制恢复,只需要找到故障分片最后一个global checkpoint(在该点之前所有的数据操作在所有数据分片上都已经是一致的,无需进行再处理),以此为起点将新主分片上之后发生的差异数据同步过来就可以完成全部的数据恢复(数据的差异信息来源于新主分片上的translog),达到数据一致点,整个恢复过程所需的工作量极大减少,数据恢复所需的时间也极大缩短。

参考: https://www.modb.pro/db/33685