这一部分是实现快照,测试分为两部分:
./raft
./kv/test_raftstore
首先来看raft部分的修改
首先是Append的过程中,如果要发送的Entry已经被压缩进快照了,这时候就需要发送快照。
func (r *Raft) sendAppend(to uint64) {
// ...
msg.LogTerm, err = r.RaftLog.Term(msg.Index)
if err != nil {
// ...
} else if err == ErrCompacted {
snapshot, err := r.RaftLog.Snapshot()
if err != nil {
return
}
msg.Snapshot = &snapshot
msg.MsgType = pb.MessageType_MsgSnapshot
}
r.msgs = append(r.msgs, msg)
}
其次是handleSnapshot,更新Raft状态。
func (r *Raft) handleSnapshot(m pb.Message) {
meta := m.Snapshot.Metadata
if m.Term < r.Term || meta.Index <= r.RaftLog.committed {
return
}
r.becomeFollower(m.Term, m.From)
r.RaftLog.committed = meta.Index
r.RaftLog.pendingSnapshot = m.Snapshot
r.RaftLog.applied = meta.Index
r.RaftLog.stabled = meta.Index
r.RaftLog.entries = nil
r.Prs = make(map[uint64]*Progress)
for _, id := range meta.ConfState.Nodes {
r.Prs[id] = &Progress{}
}
}
最后是RawNode中HasReady、Ready、Advance的修改,添加快照信息。
func (rn *RawNode) Ready() Ready {
// ...
if !IsEmptySnap(rn.Raft.RaftLog.pendingSnapshot) {
ready.Snapshot = *rn.Raft.RaftLog.pendingSnapshot
}
return ready
}
func (rn *RawNode) HasReady() bool {
// ...
if !IsEmptySnap(rn.Raft.RaftLog.pendingSnapshot) {
return true
}
}
func (rn *RawNode) Advance(rd Ready) {
// ...
rn.Raft.RaftLog.pendingSnapshot = nil
rn.Raft.RaftLog.maybeCompact()
}
func (l *RaftLog) maybeCompact() {
first, _ := l.storage.FirstIndex()
if first > l.FirstIndex() {
l.entries = l.entries[first-l.FirstIndex():]
}
}
再就是raftstore部分的修改
先回顾一下上层状态机是怎么运行的。首先是从raftWorker.run()
开始。
func (rw *raftWorker) run() {
msg := <-rw.raftCh
HandleMsg(msg)
HandleRaftReady()
}
func (d *peerMsgHandler) HandleMsg(msg message.Msg) {
switch msg.Type {
case message.MsgTypeRaftCmd:
d.proposeRaftCommand()
case message.MsgTypeTick:
d.onTick()
}
}
d.onTick()
的任务之一就是检查是否需要压缩日志,然后调用d.proposeRaftCommand()
,压缩指令过一次Raft后,会在HandleReadyState中被上层状态机应用,修改storage的TruncatedState.Index
,然后maybeCompact()
就会截断Raft层的Entries。
if msg.AdminRequest != nil {
switch msg.AdminRequest.CmdType {
case raft_cmdpb.AdminCmdType_CompactLog:
compactLog := msg.AdminRequest.GetCompactLog()
if compactLog.CompactIndex > d.peerStorage.applyState.TruncatedState.Index {
d.peerStorage.applyState.TruncatedState.Index = compactLog.CompactIndex
d.peerStorage.applyState.TruncatedState.Term = compactLog.CompactTerm
kvWB.SetMeta(meta.ApplyStateKey(d.regionId), d.peerStorage.applyState)
kvWB.WriteToDB(d.peerStorage.Engines.Kv)
d.ScheduleCompactLog(compactLog.CompactIndex)
}
}
}
这里d.ScheduleCompactLog
是创建一个Task交给raftLogGCWorker,这里是要回收记录的raft log元数据。
HandleReadyState另一个任务是SaveReadyState,SaveReadyState()
还会调用ApplySnapshot()
,更新并保存raftState、applyState、snapState、regionState,因为这里更新了regionState,所以需要添加一个Task给regionWorker,这个Task最终会由applySnap
来处理。
ch := make(chan bool, 1)
ps.regionSched <- &runner.RegionTaskApply{
RegionId: snapData.Region.Id,
Notifier: ch,
SnapMeta: snapshot.Metadata,
StartKey: snapData.Region.StartKey,
EndKey: snapData.Region.EndKey,
}
if r := <-ch; r {
return &ApplySnapResult{PrevRegion: ps.region, Region: snapData.Region}, nil
}
return nil, nil
关于RaftLog.Term()
只能说这个函数太精髓了,为什么RaftLog的0位就不能空出来,就没这么复杂了,都得看Test的脸色,太难受了。
func (l *RaftLog) Term(i uint64) (uint64, error) {
// 迎合Test
if !IsEmptySnap(l.pendingSnapshot) && i == l.pendingSnapshot.Metadata.Index {
return l.pendingSnapshot.Metadata.Term, nil
}
// 篡改error,ErrCompacted说明该发送快照了
if len(l.entries) == 0 || i < l.FirstIndex() {
term, err := l.storage.Term(i)
if err == ErrUnavailable && !IsEmptySnap(l.pendingSnapshot) {
return term, ErrCompacted
}
return term, err
}
j := i - l.FirstIndex()
if j >= uint64(len(l.entries)) {
return 0, ErrUnavailable
}
return l.entries[j].Term, nil
}
总结
只能说太复杂了,还得理解他这个系统,一个基本的RaftKV没必要这么复杂的,奈何TinyKV夹带私货,把TiKV的思想也带到了里面,写的脑壳痛。
还有要说明的是,make project2c
没能全过,依次运行每个Test可以过,应该是内存不足了,我的电脑是8G的,又开了个GoLand,估计不太够用。
#!/bin/bash
go test -v --count=1 --parallel=1 -p=1 ./raft -run 2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestOneSnapshot2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestSnapshotRecover2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestSnapshotRecoverManyClients2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestSnapshotUnreliable2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestSnapshotUnreliableRecover2C
go test -v --count=1 --parallel=1 -p=1 ./kv/test_raftstore -run TestSnapshotUnreliableRecoverConcurrentPartition2C