學習RadonDB原始碼(二)

wingsless發表於2019-05-20

1. 為我新的一天沒有放棄而喝彩

學習是一件很容易放棄的事情,因為就算是不學,我也能在現在的崗位上發光發熱。可是人不就是一個熱愛折騰的種群嗎?

今天沒有放棄不代表明天沒有放棄,也許放棄的可能性大於堅持的可能性,不管怎樣,堅持一天算一天。

RadonDB面對著TiDB,OceanBase等等資料庫的競爭,都是分散式資料庫,為什麼要首先學習RadonDB呢?畢竟這是一款真的基於MySQL而不是相容MySQL的產品,通過學習RadonDB,也許有一天我能在其原始碼上做出點什麼貢獻也未可知,我起碼對MySQL的熟悉程度更高。

2. 繼續昨天的話題

昨天我寫到了程式的主入口,注意其最重要的一句:

    // Proxy.
    proxy := proxy.NewProxy(log, flagConf, build.Tag, conf)
    proxy.Start()

一切都是從這裡開始的,為什麼這麼說呢?

這一啟動,就好像啟動了一個mysqld一樣,可以正常的接收mysql客戶端的連線請求。

根據昨天講述的,proxy的啟動實際上是執行了Accept方法,而Accept則是以服務形式啟動起來,並且監聽了幾個埠的。

那我們再來看看Accept方法:

// Accept runs an accept loop until the listener is closed.
func (l *Listener) Accept() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    for {
        conn, err := l.listener.Accept()
        if err != nil {
            // Close() was probably called.
            return
        }
        ID := l.connectionID
        l.connectionID++
        go l.handle(conn, ID, l.serverVersion)
    }
}

從程式碼邏輯上看,只要沒有執行Close,就會一直迴圈監聽下去,監聽的就是一個一個的網路連線請求。

我猜測這裡的連線就好像是我們在MySQL中執行“show processlist”的時候,顯示的資訊,每來一個連線,就會給它分配一個ID,並啟動一個監聽器的handler goroutine,可以理解為啟動了一個執行緒,這個執行緒專門負責該連線。

到這裡我們就可以肯定,RadonDB也是一個單程式多執行緒的架構,和MySQL並無二致。

現在就可以分析分析handler方法到底做了什麼。這個方法很長很長,我實在是不能一行一行的貼上過來,只是撿一些有代表性的講講。

// handle is called in a go routine for each client connection.
func (l *Listener) handle(conn net.Conn, ID uint32, serverVersion string) {}

首先映入眼簾的一定是註釋,良好的程式碼一定擁有良好的註釋。註釋告訴我們,這個handler方法是處理每個客戶端連線的。

客戶端連線嘛,每個DBA都知道,連線上來就是為了執行SQL的命令的,有一般的DDL,DML還有些指令性命令。

那麼我推斷程式碼裡一定有一個switch分支用於對每種命令進行處理:

for {
        if data, err = session.packets.Next(); err != nil {
            return
        }

        // Update the session last query time for session idle.
        session.updateLastQueryTime(time.Now())
        switch data[0] {
        // COM_QUIT
        case sqldb.COM_QUIT:
            return
            // COM_INIT_DB
        case sqldb.COM_INIT_DB:
            db := l.parserComInitDB(data)
            if err = l.handler.ComInitDB(session, db); err != nil {
                if werr := session.writeErrFromError(err); werr != nil {
                    return
                }
            } else {
                session.SetSchema(db)
                if err = session.packets.WriteOK(0, 0, session.greeting.Status(), 0); err != nil {
                    return
                }
            }
            // COM_PING
        case sqldb.COM_PING:
            if err = session.packets.WriteOK(0, 0, session.greeting.Status(), 0); err != nil {
                return
            }
            // COM_QUERY
        case sqldb.COM_QUERY:
            query := l.parserComQuery(data)
            if err = l.handler.ComQuery(session, query, nil, func(qr *sqltypes.Result) error {
                return session.writeTextRows(qr)
            }); err != nil {
                log.Error("server.handle.query.from.session[%v].error:%+v.query[%s]", ID, err, query)
                if werr := session.writeErrFromError(err); werr != nil {
                    return
                }
            }
//省略其他

還真的是有,邏輯也不復雜,其實剛才的程式碼裡沒有展現出session的概念,先講講session在回過頭來講剛才的程式碼:

session := newSession(log, ID, l.serverVersion, conn)
//省略一些session的檢查等操作

l.handler.SessionInc(session)
defer l.handler.SessionDec(session)

// Reset packet sequence ID.
session.packets.ResetSeq()

核心思想就是新建了一個session,之後,才有了剛才的操作,要從session中拿出使用者操作來,放在一個叫做data的切片中,然後判斷切片中具體的操作型別。

到這裡應該很多人都會知道,RadonDB到底做了一個什麼樣的入口了,其實就是做了一個自己的MySQL服務,監聽特定的埠,接收使用者的操作。

這裡所有的程式碼都可以參考以下這個github專案:

go-mysqlstack

作者也是RadonDB的作者之一。這個go-mysqlstack的目的也很簡單,就是實現一個mysqld:

簡介

官方給的示例,就是啟動了一個服務端:

示例

對於交付的客戶來說,其實就是在用MySQL,只不過埠有變,服務的啟動方式和配置方式不太一樣,但是寫程式碼還是用jdbc-driver,對於開發者來說沒有任何變化。

3. 小結

Go語言真有意思,利用已經成熟的專案來學習Go語言,我覺得比一點一點看書來的快一些。

當然了,學會了寫之後就要思考,思考這門語言,真的做到Thinking in Go。

真是學而不思則罔。

相關文章