學習RadonDB源碼(七)

1. 準備收官

RadonDB作爲一款優秀的分佈式數據庫產品,離不開這幾大組件:

  • 存儲
  • 計算

RadonDB的存儲由MySQL存儲節點提供,這很正常,這些存儲節點還負責了計算工作。從這個角度上來說,RadonDB還不是很典型的分佈式數據庫系統。這種架構上,計算實際上並不是由RadonDB來提供的,而是由MySQL提供的。

RadonDB的設計上還有一個精巧的地方,就是利用了TokuDB存儲了全量數據,對比較複雜的計算,需要join的地方,都由這個全量存儲來提供。因此RadonDB看着不太像一個典型的分佈式存儲系統,不過青雲實現了MySQL集羣的Raft協議,這一點還是很值得稱讚的。

昨天夜裏和今天白天我讀了很多有關Raft協議的資料,那麼今天還是來看看RadonDB是如何實現MySQL的Raft的。

2. Xenon源碼解讀

RadonDB的Raft組件叫做Xenon,在github上也有源代碼。先來看看整體的架構圖:

一個Raft集羣應該有三種狀態:Leader,Candidate和Follower。Xenon的代碼是這樣實現的:

// Leader tuple.
type Leader struct {
    *Raft
    // the smallest binlog which slaves executed by SQL-Thread
    relayMasterLogFile string

    // leader degrade to follower
    isDegradeToFollower bool

    // Used to wait for the async job done.
    wg sync.WaitGroup

    // the binlog which we should purge to
    nextPuregeBinlog string

    purgeBinlogTick   *time.Ticker
    checkSemiSyncTick *time.Ticker

    // leader process heartbeat request handler
    processHeartbeatRequestHandler func(*model.RaftRPCRequest) *model.RaftRPCResponse

    // leader process voterequest request handler
    processRequestVoteRequestHandler func(*model.RaftRPCRequest) *model.RaftRPCResponse

    // leader send heartbeat request to other followers
    sendHeartbeatHandler func(*bool, chan *model.RaftRPCResponse)

    // leader process send heartbeat response
    processHeartbeatResponseHandler func(*int, *model.RaftRPCResponse)
}

Leader要做的事情有兩件:

  • 向集羣內部發送心跳
  • 處理來自Client的請求

這些都是通過RPC來實現的。代碼中首先去新建了一個Leader:

// NewLeader creates new Leader.
func NewLeader(r *Raft) *Leader {
    L := &Leader{
        Raft: r,
    }
    L.initHandlers()
    return L
}

現在看看這些都是幹什麼的吧。

當選舉成功以後,一個節點成爲Leader,這個時候就會去調度這個方法來新建一個Leader角色。其實新建Leader也無非就做了這麼幾件事:

// leader handlers
func (r *Leader) initHandlers() {
    // heartbeat request
    r.setProcessHeartbeatRequestHandler(r.processHeartbeatRequest)

    // vote request
    r.setProcessRequestVoteRequestHandler(r.processRequestVoteRequest)

    // send heartbeat
    r.setSendHeartbeatHandler(r.sendHeartbeat)
    r.setProcessHeartbeatResponseHandler(r.processHeartbeatResponse)
}

這段代碼讓我看着有點恍惚,很像Java的setter方法,這些方法將各種request發送了出去,但是我有點不知道爲什麼要處理投票的請求,按理說這個時候投票都已經結束了,開始了自己的任期,以後如果有機會調試代碼再看看。

今天先挑一個發送心跳的方法來看看:

// leaderSendHeartbeatHandler
// broadcast hearbeat requests to other peers of the cluster
func (r *Leader) sendHeartbeat(mysqlDown *bool, c chan *model.RaftRPCResponse) {
    // check MySQL down
    if r.mysql.GetState() == mysql.MysqlDead {
        *mysqlDown = true
        return
    }

    // broadcast heartbeat
    r.mutex.RLock()
    defer r.mutex.RUnlock()

    for _, peer := range r.peers {
        r.wg.Add(1)
        go func(peer *Peer) {
            defer r.wg.Done()
            peer.sendHeartbeat(c)
        }(peer)
    }

}

因爲是基於MySQL節點做的Raft,因此首先要檢查mysqld是否存活,如果死掉了也就沒有發送心跳的意義了。這裏要說明的是,不是mysql向外發送心跳,是Xenon,因此一定要檢查mysqld的狀態先,mysqld掛掉了,就不發心跳了,就可以觸發新一輪選舉了。

發送心跳的時候要遍歷所有節點,因爲要給所有節點發心跳,每遍歷一個節點,就要給WaitGroup中增加1,waitGroup可以保證goroutine安全的結束。

這之後自然就是調用發送的方法,向集羣內廣播心跳了。

3. Go語言相關

Go語言有個很有意思的地方叫做函數類型。也就是說可以把變量指定成函數類型,比如這樣:

package main

import "fmt"

func test(param string) {
    fmt.Println(param)
}

func main()  {
    t := test
    t("quan")
}

我暫時還沒有想明白函數變量的好處,都說是讓代碼變得更靈活。

這段代碼中還有一個waitGroup,參考一下下面的代碼:

package main

import (
    "fmt"
    "sync"
)
var wg sync.WaitGroup

func test(param int) {
    defer wg.Done()
    fmt.Println(param)
}

func main()  {
    for i := 1; i < 10; i++ {
        wg.Add(1)
        go test(i)
    }
    wg.Wait()
}

注意,Add和Done一定要配對,不然一定報deadlock錯誤。

現在畫張圖看看原理:

所有的現成完成之後都會去等待,並在此時將wg的計數器減1,計數器被減到0時,等待結束。

4. 小結

其實要是學習Raft的實現的話,我這個系列可能還得再寫一段時間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章