TinyKV Project2C 关于 Snapshot 调用流程的问题

看了好几天,搞不清整个 Snapshot 的调用流程,有大佬讲解下嘛。
我说说我目前的理解:

  1. 塞日志,到达 RaftLogGcCountLimit,发送一个 raft_cmdpb.AdminRequest 去 proposeRaftCommand
  2. 然后绕了一圈,raft commit 后,准备 apply,在 HandleRaftReady 中处理 raft_cmdpb.AdminCmdType_CompactLog 更新 applyState,然后写入 KV 持久化。
  3. 然后调用 ScheduleCompactLog 截断日志(但是它并没有真的产生 snapshot,纯粹就一个截断了 log)

好了,后面的就不知道了
如何如何更新 raft 里面的 apply 信息?
raft 中 leader 发现一个 follower 落后,是怎么个流程发送?
何时生成了 snapshot? 毕竟ScheduleCompactLog 里面只是一个截断而已.
然后又是怎么个异步发送 snapshot
请大牛讲解下,直接说方法名调用链就可以了。

可以这样理解:
1 compact log会定时截断日志
2 如果新加入节点,或者 follower大幅度落后leader, 会出现日志找不到的情况(已经被截断:),这个时候就可以发送snapshot了
3 如何产生一个snapshot? storage.snapshot()方法就可以生成一个snapshot

1赞

比较啰嗦:(

RaftLogGC过程 和 快照生成没有关系。也就是说整个RaftLogGC这一过程没有快照生成的步骤。快照生成是在Raft层驱动的,也可以看成是RaftLogGC过程的结果触发了快照。先说一下两个过程的大概步骤。

RaftLogGC的过程:
(1)在 raft_worker.go 中的 HandleMsg 负责处理 raftCh 中收到的消息,这里假设是message.MsgTypeTick消息。因此触发 onTick 函数、onRaftGCLogTick函数。

(2)从 onRaftGCLogTick 函数开始,当发现已经 applied 的日志超过 RaftLogGcCountLimit,就会发送一个 CompactLogRequestAdminRequest

(3)这个消息通过 proposeRaftCommand 到了 rawnode.go 中的 Propose 函数,进入了raft模块,封装成日志,等待复制,提交等。

(4)在 raft_worker.go 中的 HandleRaftReady,负责将raft层中的更改通过 Ready 来通知上层。因此,这里需要apply包含 AdminCmdType_CompactLog 的日志项。

(5)这个时候需要更新applyState的信息,并且调用 ScheduleCompactLog 生成一个gc Task。

(6)worker.go 中的 Start 函数通过 receiver 接收任务,在 raftlog_gc.go 中的 Handle 函数中处理,删除 badger 中的一些元信息。

(7)最后是通过 rownode.go 中的 Advance 更新raft中的apply等变量。

snapshot的发送流程:

(1)raft.go 中的 sendAppend 函数中,调用 raftLog.storage.Snapshot() 函数,新建一个 RegionTaskGen 的任务,异步产生快照,并且将 snapState 的状态改为 Generating 表示正在产生快照。另外,这次调用应该返回一个快照暂时不能获得的Error。

(2) region_task.go 中的 handleGen 函数负责产生快照,调用 doSnapshot 产生快照。

(3)当下次调用 Snapshot() 的时候,如果快照已经产生了,则接收并验证快照,返回结果。

(4)那么 raft.gosnapshotMsg 被存储在了 msgs 中。等待通过 Ready 处理。

(5) HandleRaftReady 中通过调用 send 通过 transport 将消息封装成 RaftMessage 发送到对应的store。

(6)通过 transport.goServerTransportsend 函数发送。在 SendStore 中调用 Resolve ,产生一个 resolveAddrTask 任务,通过 HandlegetAddr 获得StoreId的地址,并通过 callback 更新 raftClientaddrs

(7)在 WriteData 函数中,由于是发送快照的消息,所以通过 SendSnapshotSock 函数建立一个 sendSnapTask 任务。

(8)在 snap_runner.go 中,通过 sendSnap 发送快照。。。。。。。

见(7)通过调用 Advance 将结果写回到raft层中。

发送快照是因为Leader当中已经没有了需要发送给Follower的日志项(通过Leader存储的每个Follower的NextIndex来判断)。这其实是在SendAppendMsg中,需要改一下发送流程,如果没有这个日志项(Leader.firstIndex>Prs[follower].nextIndex 大概是这个意思)就需要发送快照了,调用storage.snapshot()方法生成。。。。。。

生成snapshot见上面一个问题。

ScheduleCompactLog 里面真的就只是一个截断而已.

(1)raft中调用 peer_storage.go 中的 snapshot 函数(假设)。生成一个RegionTaskGen的快照任务。
(2)region_task.go 中的 Handle 函数通过调用 handleGen 产生快照。
。。。。(可以自己看看怎么生成的,有时间继续写。。。)
(3)在下一次 Raft 调用 Snapshot时,会检查快照生成是否完成。如果是,Raft应该将快照信息发送给其他 peer,而快照的发送和接收工作则由 snap_runner.go 处理(异步)。

9赞

首先感谢你这么详细的回答,helped a lot!

请问发送快照的时候,他是如何将 msgs 里面的 snapshot 通过 snap_runner.go 进行发送?

我猜是在发送的时候,上层会把这个 msg 自动替换掉,然后通过 snap_runner 发送,不知道是不是这样的。

上面我改了一下,snapshot的发送流程应该可以回答你的问题。

请问什么时候出发生成snapshot

在发现需要发给follower的日志已经被compact的时候,或者一个新节点启动需要快速恢复的时候