日志保证连续的好处
保证状态一致性
对于增量操作日志,连续顺序保证最终的结果正确性
简化日志一致性检查
确定某一个索引日志是一致的,这个日志的下一个有冲突即可保证这个日志之前都是一致的,不然不保证连续需要逐个比较。
领导选举时需要选择日志完整性最好的节点,如果日志不连续,一个123。一个124将无法判断哪个日志完整性很好,所以要么是123,要么是1234这样,只需比较谁的索引和任期更大即可,实现起来更简单高效。
简化日志恢复
日志主从恢复时确定某一个索引日志是一致的,这个日志的下一个有冲突即可保证这个日志之前都是完整的,只需要截断恢复这个日志后的内容即可,不然如果日志不连续,需要逐个比较
简化代码实现
用数组即可实现,便于索引定位,代码更简洁遍历
如何保证日志是连续的
主节点按顺序写日志没啥难度
同步从节点时需要注意,从节点需要判断主节点来的日志和自己本地的日志是否能连续接上,可以理解同步日志不是简单告诉从节点复制哪些日志,而是说把哪几条日志接在哪一条日志后面来保证从节点也和主节点一样日志是连续的,具体实现代码可以查看etcd的源代码部分,查看log.go里的maybeAppend方法
// maybeAppend returns (0, false) if the entries cannot be appended. Otherwise,
// it returns (last index of new entries, true).
func (l *raftLog) maybeAppend(index, logTerm, committed uint64, ents ...pb.Entry) (lastnewi uint64, ok bool) {
//index,logTerm是需要复制的新日志信息的上一条日志的信息,比如需要复制索引4,这个则为日志3
//如果日志3从节点这里不存或者不匹配就会直接发回false了,这里保证了复制新日志的时候上一个日志在从节点这里是和主节点一致的
//保证了主从节点复制日志的时候连续一致性,只要主节点自己发的日志是连续的,从节点这里也肯定是连续的了
//可以理解同步日志不是简单告诉从节点复制哪些日志,而是说把哪几条日志接在哪一条日志后面来保证主从日志一致性
if l.matchTerm(index, logTerm) {
//这里也是以日志连续性为基础进行计算,日志连续简化代码实现
lastnewi = index + uint64(len(ents))
//验证需要复制的新日志里有没有冲突
ci := l.findConflict(ents)
switch {
//0 表示没有新日志需要复制
case ci == 0:
//表示在从节点已提交的日志里有和主节点不一致的,数据有冲突
case ci <= l.committed:
l.logger.Panicf("entry %d conflict with committed entry [committed(%d)]", ci, l.committed)
//表示有新的日志需要复制,就会取ci后的数据来进行复制
default:
offset := index + 1
l.append(ents[ci-offset:]...)
}
//恢复数据时可能主节点的commit大于发来的日志最大索引
//正常复制时commit是<=发来的日志最大索引的
//因此取最小值进行提交
l.commitTo(min(committed, lastnewi))
return lastnewi, true
}
return 0, false
}
查看findConflict源码,原版注释很清楚了,不再赘述
// findConflict finds the index of the conflict.
// It returns the first pair of conflicting entries between the existing
// entries and the given entries, if there are any.
// If there is no conflicting entries, and the existing entries contains
// all the given entries, zero will be returned.
// If there is no conflicting entries, but the given entries contains new
// entries, the index of the first new entry will be returned.
// An entry is considered to be conflicting if it has the same index but
// a different term.
// The index of the given entries MUST be continuously increasing.
func (l *raftLog) findConflict(ents []pb.Entry) uint64 {
for _, ne := range ents {
if !l.matchTerm(ne.Index, ne.Term) {
if ne.Index <= l.lastIndex() {
l.logger.Infof("found conflict at index %d [existing term: %d, conflicting term: %d]",
ne.Index, l.zeroTermOnErrCompacted(l.term(ne.Index)), ne.Term)
}
return ne.Index
}
}
return 0
}