1. 可能是開始也可能是結束 RadonDB是國內知名雲服務提供商青雲開源的一款產品,下麵是一段來自官方的介紹: QingCloud RadonDB 是基於 MySQL 研發的新一代分散式關係型資料庫,可無限水平擴展,支持分散式事務,具備金融級數據強一致性,滿足企業級核心資料庫對大容量、高併發、高可 ...
1. 可能是開始也可能是結束
RadonDB是國內知名雲服務提供商青雲開源的一款產品,下麵是一段來自官方的介紹:
QingCloud RadonDB 是基於 MySQL 研發的新一代分散式關係型資料庫,可無限水平擴展,支持分散式事務,具備金融級數據強一致性,滿足企業級核心資料庫對大容量、高併發、高可靠及高可用的極致要求。
做DBA的都知道關係型資料庫在分散式資料庫方面堪稱舉步維艱,雖然很多高手或者公司都開源了自己的中間件,但是很少有公司像青雲這樣將自己商用的成套解決方案直接開源的。可能開源版本和商用版本之間有很多功能差異,不過從解決方案的完整性角度來看,RadonDB堪稱是良心產品了。
而且RadonDB的還有一個明顯的好處是用Go編寫的,而且現在的代碼量也不算大,對於一個學習Go語言的人來說這是一個極好的項目。另外還有一點,RadonDB模擬了完整的MySQL Server端,裡面有一項核心的東西叫做SQL解析器和優化器的,剛好可以藉此機會從源碼角度學習一下其思想。要知道MySQL雖然開源,但是整個項目都是用C編寫的,很難看懂。
我打算用閑暇時間好好學習一下RadonDB源碼,當然我可能半途而廢,所以,這一篇可能是開始也可能是結束。
2. 入口的radon.go文件
這個文件在“radon/src/radon”目錄下,代碼只有區區82行,不過這是整個RadonDB的入口。
這段代碼中利用了不少flag包用於接收參數,首先映入眼帘的是一堆import,此處就不加贅述了,因為畢竟只是引入了包,至於做什麼的,代碼寫了就能知道。
接下來是包的初始化:
var (
flagConf string
)
func init() {
flag.StringVar(&flagConf, "c", "", "radon config file")
flag.StringVar(&flagConf, "config", "", "radon config file")
}
flag是一個很好用的包,用於接收命令行參數,至於怎麼用可以參考網上的資料。這個init()函數很有意思,這個函數會在很多書的“包初始化”一節來講述,其實記住幾個順序就可以:
- 初始化導入的包;
- 在包級別為聲明的變數計算並分配初始值;
- 執行包內的init函數。
這是包的初始化順序,那麼回到radon.go,初始化順序也是一目瞭然的。
init函數不能被引用
接下來是一個簡單的usage函數:
func usage() {
fmt.Println("Usage: " + os.Args[0] + " [-c|--config] <radon-config-file>")
}
僅僅是為了列印命令行的幫助,在引用的時候才有效,現在只是聲明。
而後就是程式的主入口main函數了,這段函數的最開始就執行了這樣一句:
runtime.GOMAXPROCS(runtime.NumCPU())
聲明瞭邏輯處理單元,數量和CPU核數相當,這一點在之前講goroutine的筆記中講述過。
緊接著,程式將獲得一些關鍵的環境信息:
build := build.GetInfo()
雖然只有一句,但是背後的東西還是很豐富的:
func GetInfo() Info {
return Info{
GoVersion: runtime.Version(),
Tag: "8.0.0-" + tag,
Time: time,
Git: git,
Platform: platform,
}
}
這是一種典型的結構體的初始化方式,如果對結構體不熟悉,建議也是百度一下相關資料。
這些列印出信息的東西無非就是一些顯示輸出,跟我們平時啟動Spring的時候列印那個炫酷的SPRING banner沒什麼區別,接來下才是處理一些要緊的東西,比如處理配置:
// config
flag.Usage = func() { usage() }
flag.Parse()
if flagConf == "" {
usage()
os.Exit(0)
}
conf, err := config.LoadConfig(flagConf)
if err != nil {
log.Panic("radon.load.config.error[%v]", err)
}
log.SetLevel(conf.Log.Level)
其中的flag.Usage
是函數變數,函數變數是一個新穎的概念,舉一個例子說明:
func square(n int) int { return n*n }
f := square
//列印9
fmt.Println(f(3))
flag包中的Usage本身就是個函數變數。
上面這段業務代碼主要做了這麼幾件事情:
- 解析flag,得到命令行參數;
- 判斷參數是否為空,為空則列印使用說明並退出;
- 載入配置項,並做異常處理;
- 設置日誌級別。
我們先不說緊接著要啟動的Monitor了,這是一個性能指標監控,並不在我的學習範圍內。
// Proxy.
proxy := proxy.NewProxy(log, flagConf, build.Tag, conf)
proxy.Start()
代理是每個人寫程式都挺喜歡寫的名字。proxy是一個自行編寫的包,我們來看看NewProxy的時候做了什麼:
func NewProxy(log *xlog.Log, path string, serverVersion string, conf *config.Config) *Proxy {
audit := audit.NewAudit(log, conf.Audit)
router := router.NewRouter(log, conf.Proxy.MetaDir, conf.Router)
scatter := backend.NewScatter(log, conf.Proxy.MetaDir)
syncer := syncer.NewSyncer(log, conf.Proxy.MetaDir, conf.Proxy.PeerAddress, router, scatter)
plugins := plugins.NewPlugin(log, conf, router, scatter)
return &Proxy{
log: log,
conf: conf,
confPath: path,
audit: audit,
router: router,
scatter: scatter,
syncer: syncer,
plugins: plugins,
sessions: NewSessions(log),
iptable: NewIPTable(log, conf.Proxy),
throttle: xbase.NewThrottle(0),
serverVersion: serverVersion,
}
}
這段代碼倒是很簡單,就是利用入參中的配置項,聲明瞭一系列的變數,並將這些變數封裝在一個結構體內,然後返回。至於這些變數都是乾什麼的,我下次再說,這次只跟蹤主流程。
緊接著看看啟動都做了什麼:
// Start used to start the proxy.
func (p *Proxy) Start() {
log := p.log
conf := p.conf
audit := p.audit
iptable := p.iptable
syncer := p.syncer
router := p.router
scatter := p.scatter
plugins := p.plugins
sessions := p.sessions
endpoint := conf.Proxy.Endpoint
throttle := p.throttle
serverVersion := p.serverVersion
log.Info("proxy.config[%+v]...", conf.Proxy)
log.Info("log.config[%+v]...", conf.Log)
if err := audit.Init(); err != nil {
log.Panic("proxy.audit.init.panic:%+v", err)
}
// 省略了一大堆,為了節省篇幅
spanner := NewSpanner(log, conf, iptable, router, scatter, sessions, audit, throttle, plugins, serverVersion)
if err := spanner.Init(); err != nil {
log.Panic("proxy.spanner.init.panic:%+v", err)
}
svr, err := driver.NewListener(log, endpoint, spanner)
if err != nil {
log.Panic("proxy.start.error[%+v]", err)
}
p.spanner = spanner
p.listener = svr
log.Info("proxy.start[%v]...", endpoint)
go svr.Accept()
}
這個Start函數看起來好像Java中的構造器,做的事情也和構造器有點相似,就是賦值,不過它還能做多的事情,比如說啟動了一個監聽:
svr, err := driver.NewListener(log, endpoint, spanner)
有了監聽之後,就可以啟動一個goroutine了,而且是有條件的存活的:
go svr.Accept()
這裡的條件就是Accept要做什麼:
Accept runs an accept loop until the listener is closed.
在listener關閉之前,Accept將始終運行一個迴圈,也就是說這個goroutine會一直生存下去。
到這一步proxy就算啟動起來了,然後就會去啟動Admin了:
// Admin portal.
admin := ctl.NewAdmin(log, proxy)
admin.Start()
按照慣例看看NewAdmin在乾什麼:
// NewAdmin creates the new admin.
func NewAdmin(log *xlog.Log, proxy *proxy.Proxy) *Admin {
return &Admin{
log: log,
proxy: proxy,
}
}
代碼邏輯很簡單,就是返回一個Admin結構體的指針。而Admin結構體是這樣的:
// Admin tuple.
type Admin struct {
log *xlog.Log
proxy *proxy.Proxy
server *http.Server
}
看,之前的代碼里沒有對server進行賦值,這是為什麼?答案在Start函數里:
// Start starts http server.
func (admin *Admin) Start() {
api := rest.NewApi()
router, err := admin.NewRouter()
if err != nil {
panic(err)
}
api.SetApp(router)
handlers := api.MakeHandler()
admin.server = &http.Server{Addr: admin.proxy.PeerAddress(), Handler: handlers}
go func() {
log := admin.log
log.Info("http.server.start[%v]...", admin.proxy.PeerAddress())
if err := admin.server.ListenAndServe(); err != http.ErrServerClosed {
log.Panic("%v", err)
}
}()
}
這裡是一系列的Http操作,對server的賦值就在其中,此時會把預設IP,埠等等信息都寫入到server中:
一看代碼我就知道RadonDB要用3308埠進行連接,而起管理埠就註冊在8080。
好了,這些都很容易明白,此時Start函數只需要啟動一個goroutine就可以了。關鍵在這裡:
看名字就知道這是乾什麼的,監聽並維護一個服務,看看其註釋:
那麼這樣一來,服務就啟動起來了,當然後面還會有stop函數,就不再詳解了。有意思的是,可以註意這幾句:
// Handle SIGINT and SIGTERM.
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
log.Info("radon.signal:%+v", <-ch)
這幾句聲明瞭一個通道,一個Signal類型的通道,可以用於接收系統調用,SIGINT一般是ctrl-c,SIGTERM一般是kill。在發生這兩個系統調用後,系統開始關閉。
3. 小結
Go語言還是簡單的,至少現在看來,這些代碼我是都能看懂的,而我學習Go語言的時間也不過兩周。
我希望能藉著RadonDB的開源,學會關鍵的優化器和SQL解析器的思想。