介绍使用TLA+对Raft的验证工作。
运行
容易发现,源码里面不包含TLC的Config,但是我们在Issue 1里面能找到一个。
1 | CONSTANTS Server = {r1,r2,r3} |
需要限制一下Model
1 | /\ \A i \in Server : Len(log[i]) =< maxLength |
timeoutNum必须至少为1,不然不能触发选举
列出一下Safety条件
1 | OneLeader == Cardinality( {i \in Server : state[i] = Leader} ) <= 1 |
注意如下错误
1 | The subscript of the next-state relation specified by the specification |
看看xxx是不是在vars里面
1 | Spec == Init /\ [][Next]_vars |
打印语句
1 | Print(内容, Print语句的值) |
发现maxMessage必须为1,其他都可以变。
总体模型
变量介绍
下面的变量是全局的:
messages
表示从一个Server发送到另一个Server的消息。因为TLAPS不支持Bags模块(也就是所谓的multiset),所以这是一个将消息映射为Nat的函数。
通过WithMessage(m, msgs)
往messages
添加一条消息,其中m
是我们需要发送的消息,msgs
就是messages
这个变量。1
2
3
4
5WithMessage(m, msgs) ==
IF m \in DOMAIN msgs THEN
[msgs EXCEPT ![m] = msgs[m] + 1]
ELSE
msgs @@ (m :> 1)消息的格式如下图所示
1
2
3
4
5
6[mtype |-> RequestVoteRequest,
mterm |-> currentTerm[i],
mlastLogTerm |-> LastTerm(log[i]),
mlastLogIndex |-> Len(log[i]),
msource |-> i,
mdest |-> j]elections
仅在证明(proof)中需要,实现(implementation)并不需要。
这个用来记录所有成功的选举。包括Leader和Voter的日志。其中一个元素类似下面。1
2
3
4
5{[ eterm |-> currentTerm[i],
eleader |-> i,
elog |-> log[i],
evotes |-> votesGranted[i],
evoterLog |-> voterLog[i]]}allLogs
同样仅在证明中需要。
集合了每个Server上的日志1
/\ allLogs' = allLogs \cup {log[i] : i \in Server}
下面的变量是Per Server的:
serverVars
为<<currentTerm, state, votedFor>>
在Init中,currentTerm初始化为1,state初始化为Follower,voteFor初始化为Nil。logVars
为<<log, commitIndex>>
其中log是A Sequence of log entries,log[i]
表示Server i上的日志。
在Init中,log初始化为1
log = [i \in Server |-> << >>]
每个Log Entry的格式是
1
2[term |-> currentTerm[i],
value |-> v]commitIndex初始化为0。
candidateVars
为<<votesResponded, votesGranted, voterLog>>
leaderVars
为<<nextIndex, matchIndex, elections>>
在Init中,nextIndex全部被初始化为1,matchIndex全部被初始化为0。
Helper
Quorum实际上是一个集合的集合。表示Server中所有可以形成Quorum的子集。
1 | Quorum == {i \in SUBSET(Server) : Cardinality(i) * 2 > Cardinality(Server)} |
周边Action
Spec
1 | Next == /\ \/ \E i \in Server : Restart(i) |
主要Acttion
AppendEntries
Enabling条件,就是自己是Leader,并且不能自己给自己发消息。SubSeq(s,m,n)
表示<< s[m], s[m+1], ..., s[n] >>
1 | AppendEntries(i, j) == |
对端处理AppendEntries
1 | HandleAppendEntriesRequest(i, j, m) == |
下面处理分为三个部分:
拒绝
1 | ... |
切换为Follower
1 | ... |
接受
1 | ... |
AppendEntries处理对端返回
RequestVote
Reference
- https://github.com/dranov/raft-tla/blob/master/thirdparty/raft_membership.tla
Raft的Membership Change证明 - https://github.com/dranov/raft-tla/blob/master/thirdparty/raft_dricketts.tla
一个带有很多Inv的Raft的TLA+ - https://github.com/tlaplus/CommunityModules/blob/master/modules/SequencesExt.tla
Sequences扩展