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專案:
作者也是RadonDB的作者之一。這個go-mysqlstack的目的也很簡單,就是實現一個mysqld:
官方給的示例,就是啟動了一個服務端:
對於交付的客戶來說,其實就是在用MySQL,只不過埠有變,服務的啟動方式和配置方式不太一樣,但是寫程式碼還是用jdbc-driver,對於開發者來說沒有任何變化。
3. 小結
Go語言真有意思,利用已經成熟的專案來學習Go語言,我覺得比一點一點看書來的快一些。
當然了,學會了寫之後就要思考,思考這門語言,真的做到Thinking in Go。
真是學而不思則罔。