MySQL 半同步 與Raft對比

aoerqileng發表於2021-09-09

MySQL的after_sync半同步與raft 保證一致性的方式有些類似。


after_sync是master在sync binlog後等待從庫把事務寫到relay log後的ack,拿到ack後,在commit,然後在返回給客戶端提交成功的資訊。


raft中的日誌有commit和applied 兩個列表,commited 代表日誌寫入了檔案,applied代表日誌應用到了狀態機。


raft也是leader先寫日誌,然後在等待大部分的節點資訊都寫入了日誌,提交了,然後leader在提交,提交日誌後,leader在應用到狀態機,然後在返回給客戶端提交成功的資訊, 給其他節點提交資訊,其他節點應用日誌到狀態機,其他節點網路慢的情況下,leader會不停重試傳輸。


針對leader1當機的幾種狀態下的故障。參考

https://www.cnblogs.com/mindwind/p/5231986.html




場景3中,新主會提交之前的日誌,客戶端在重試,不是重複執行了嗎?狀態機執行了2次命令。

這個狀態機不會重複執行,因為之前的leader已經掛了,還沒有apply log,所以不會傳送apply 的請求出去。

如果leader apply log,返回給客戶端確認,但是follower沒有收到apply的訊號,leader就掛了,雖然新主上有commit的日誌,但是不會apply,怎麼辦?

這個是會應用的,新的leader,在接受使用者請求之前會執行

r. dispatchLogs([]* logFuture{ noop})

然後會執行

func (r *Raft) processLogs(index uint64, future *logFuture) {
// Reject logs we've applied already
lastApplied := r.getLastApplied()
if index <= lastApplied {
r.logger.Printf("[WARN] raft: Skipping application of old log: %d", index)
return
}
// Apply all the preceding logs
for idx := r.getLastApplied() + 1; idx <= index; idx++ {
// Get the log, either from the future or from our log store
if future != nil && future.log.Index == idx {
r.processLog(&future.log, future, false)
} else {
l := new(Log)
if err := r.logs.GetLog(idx, l); err != nil {
r.logger.Printf("[ERR] raft: Failed to get log at %d: %v", idx, err)
panic(err)
}
r.processLog(l, nil, false)
}
// Update the lastApplied index and term
r.setLastApplied(idx)
}

在進行

for idx := r.getLastApplied() + 1; idx <= index; idx++

這個判斷的時候,lastapplied一定比index小,index就是lastindex,這樣已經是提交狀態的日誌會被applied。


 

raft實現了cap中的cp ,沒有保證a ,a代表的是使用者的請求一定有響應,在出現腦裂的情況下,如果一個leader的請求沒有被大多數節點接受,那麼就沒有辦法提交,沒法給客戶響應,如果3個節點中有2個節點掛掉,就剩一個節點,其實這個時候請求過來後,判斷不是主,不會執行,也會返回給客戶端not leader,所以這裡的響應,是指的不會處理請求,進行業務邏輯處理。

在raft程式碼中,我們可以看到是在apply log後才向客戶端傳送的響應

case commitEntry := <-r.fsmCommitCh:--透過這個channel能找到commit後向這個channel傳送的資料
// Apply the log if a command
var resp interface{}
if commitEntry.log.Type == LogCommand {
start := time.Now()
resp = r.fsm.Apply(commitEntry.log)
metrics.MeasureSince([]string{"raft", "fsm", "apply"}, start)
}
// Update the indexes
lastIndex = commitEntry.log.Index
lastTerm = commitEntry.log.Term
// Invoke the future if given。--給客戶端返回資訊
if commitEntry.future != nil {
commitEntry.future.response = resp
commitEntry.future.respond(nil)
}

follower的冪等實現,就是判斷entries的第一個index,是否小於等於當前follower的最後一個logindex,如果是,把這之間的刪除掉

// Process any new entries
if n := len(a.Entries); n > 0 {
start := time.Now()
first := a.Entries[0]
last := a.Entries[n-1]
// Delete any conflicting entries
lastLogIdx, _ := r.getLastLog()
if first.Index <= lastLogIdx {
r.logger.Printf("[WARN] raft: Clearing log suffix from %d to %d", first.Index, lastLogIdx)
if err := r.logs.DeleteRange(first.Index, lastLogIdx); err != nil {
r.logger.Printf("[ERR] raft: Failed to clear log suffix: %v", err)
return
}
}




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25719946/viewspace-2791179/,如需轉載,請註明出處,否則將追究法律責任。

相關文章