1. 為我新的一天沒有放棄而喝彩 學習是一件很容易放棄的事情,因為就算是不學,我也能在現在的崗位上發光發熱。可是人不就是一個熱愛折騰的種群嗎? 今天沒有放棄不代表明天沒有放棄,也許放棄的可能性大於堅持的可能性,不管怎樣,堅持一天算一天。 RadonDB面對著TiDB,OceanBase等等資料庫的競 ...
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。
真是學而不思則罔。