RadonDB是國內知名雲服務提供商青雲開源的一款產品,下面是一段來自官方的介紹:git
QingCloud RadonDB 是基於 MySQL 研發的新一代分佈式關係型數據庫,可無限水平擴展,支持分佈式事務,具有金融級數據強一致性,知足企業級核心數據庫對大容量、高併發、高可靠及高可用的極致要求。數據庫
作DBA的都知道關係型數據庫在分佈式數據庫方面堪稱舉步維艱,雖然不少高手或者公司都開源了本身的中間件,可是不多有公司像青雲這樣將本身商用的成套解決方案直接開源的。可能開源版本和商用版本之間有不少功能差別,不過從解決方案的完整性角度來看,RadonDB堪稱是良心產品了。api
並且RadonDB的還有一個明顯的好處是用Go編寫的,並且如今的代碼量也不算大,對於一個學習Go語言的人來講這是一個極好的項目。另外還有一點,RadonDB模擬了完整的MySQL Server端,裏面有一項核心的東西叫作SQL解析器和優化器的,恰好能夠藉此機會從源碼角度學習一下其思想。要知道MySQL雖然開源,可是整個項目都是用C編寫的,很難看懂。session
我打算用閒暇時間好好學習一下RadonDB源碼,固然我可能半途而廢,因此,這一篇多是開始也多是結束。併發
這個文件在「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()函數頗有意思,這個函數會在不少書的「包初始化」一節來說述,其實記住幾個順序就能夠:oop
這是包的初始化順序,那麼回到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自己就是個函數變量。
上面這段業務代碼主要作了這麼幾件事情:
咱們先不說緊接着要啓動的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。在發生這兩個系統調用後,系統開始關閉。
Go語言仍是簡單的,至少如今看來,這些代碼我是都能看懂的,而我學習Go語言的時間也不過兩週。
我但願能借着RadonDB的開源,學會關鍵的優化器和SQL解析器的思想。