學習RadonDB源碼(一)

来源:https://www.cnblogs.com/wingsless/archive/2019/05/15/10872892.html
-Advertisement-
Play Games

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()函數很有意思,這個函數會在很多書的“包初始化”一節來講述,其實記住幾個順序就可以:

  1. 初始化導入的包;
  2. 在包級別為聲明的變數計算並分配初始值;
  3. 執行包內的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解析器的思想。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 【併發編程】Future模式添加Callback及Promise 模式 ...
  • 我買的是Mac Mini(late 2014)中配,內置5400轉1T機械硬碟,該配置即使到了2019年安裝macOS Mojave系統依舊是夠用的,但硬碟嚴重拖累了運行的速度。之前考慮到更換內置sata介面硬碟的操作略微繁瑣,因此使用外置SSD的方式,將系統安裝在外置SSD上,速度的確得到了提升,... ...
  • 1. 獲取數據 1.1 導入sklearn數據集 sklearn中包含了大量的優質的數據集,在你學習機器學習的過程中,你可以通過使用這些數據集實現出不同的模型,從而提高你的動手實踐能力,同時這個過程也可以加深你對理論知識的理解和把握。(這一步我也亟需加強,一起加油!^-^) 首先呢,要想使用skle ...
  • 大家晚上好,最近忙每天忙於項目沒有時間更新自己的博客,時間就是海綿嘛硬擠擠就是有的,咂看標題" 流程圖 ",編程界的一個不可或缺的技能,特別是在做複雜的邏輯的時候要處理好每一步的關係,在數據中講就是數據之間的關聯關係,或者關聯模型等,通俗點也就是父子,母子等關係。 首先給大家介紹幾款畫流程圖的軟體: ...
  • 面向對象 面向對象的思想就是值我們要實現一個共功能的時候,我們不自己去做,而是找別人幫我們去做,幫我們去做的這個人就是對象。面向對象強調的是誰來幫我實現這個功能。 類與對象的關係 類 :是一組相關屬性和行為的集合,類可以看成是事物的模板 對象 :對象是一類事物的具體體現,對象是類的一個實列,必然具備 ...
  • 1. 內容 1. 匿名函數:一句話函數,比較簡單的函數。 函數名 = lambda 參數 : 返回值 1. 此函數不是沒有名字,他是有名字的,他的名字就是你給其設置的變數,比如func。 func() 函數執行 2. lambda 是定義匿名函數的關鍵字,相當於函數的def. 3. lambda 後 ...
  • 一、unittest模塊官方文檔: https://docs.python.org/3/library/unittest.html 二、一張圖看懂unittest: 三、Unittest主要方法屬性: 1.unittest.TestCase:TestCase類,所有測試用例繼承的基本類: class ...
  • // 導包 import java.util.Scanner; public class Sort { public static void main(String[] args) { // 創建鍵盤輸入對象 Scanner sc = new Scanner(System.out.println); ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...