学习RadonDB源码(一)
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解析器的思想。
学习RadonDB源码(一)的更多相关文章
- 学习RadonDB源码(二)
1. 为我新的一天没有放弃而喝彩 学习是一件很容易放弃的事情,因为就算是不学,我也能在现在的岗位上发光发热.可是人不就是一个热爱折腾的种群吗? 今天没有放弃不代表明天没有放弃,也许放弃的可能性大于坚持 ...
- 学习RadonDB源码(三)
1. 所谓第四代语言 SQL是一种典型的第四代语言,即4GL,这种语言的突出特点是编写者不需要关注怎么做,只需要告诉系统我要什么就可以. 虽然4GL是这样的一种语言,大大简化了编写者的编写难度,其实底 ...
- 【菜鸟学习jquery源码】数据缓存与data()
前言 最近比较烦,深圳的工作还没着落,论文不想弄,烦.....今天看了下jquery的数据缓存的代码,参考着Aaron的源码分析,自己有点理解了,和大家分享下.以后也打算把自己的jquery的学习心得 ...
- WEB前端开发学习:源码canvas 雪
WEB前端开发学习:源码canvas 雪 双旦节要到了,程序员们为了响应气氛,特别用代码制作了动态雪花,WEB前端开发学习的初学者们一起跟着案例做一遍吧! <!DOCTYPE html> ...
- 原生JS研究:学习jquery源码,收集整理常用JS函数
原生JS研究:学习jquery源码,收集整理常用JS函数: 1. JS获取原生class(getElementsByClass) 转自:http://blog.csdn.net/kongjiea/ar ...
- DotNetty网络通信框架学习之源码分析
DotNetty网络通信框架学习之源码分析 有关DotNetty框架,网上的详细资料不是很多,有不多的几个博友做了简单的介绍,也没有做深入的探究,我也根据源码中提供的demo做一下记录,方便后期查阅. ...
- 学习 vue 源码 -- 响应式原理
概述 由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教. vue 主要通过 Watcher.Dep 和 Observer 三个类来实现响应式视图.另外还有一个 sch ...
- Linux学习之源码包安装与脚本安装(十八)
Linux学习之源码包安装与脚本安装 目录 源码包与RPM包的区别 源码包安装 脚本安装 源码包与RPM包的区别 1.区别 安装之前的区别:概念上的区别 安装之后的区别:安装位置不同 源码包: 开源的 ...
- 分享学习 PHP 源码的方法
每天抽一点时间来看看 PHP 源码方面的书,说实话,无法在调试器下观察 PHP 运行状态的上下文实在是一件痛苦的事情.不过还好不是一无所获,虽然内容比较多,但是掌握方法挨着看下去还是 ...
随机推荐
- codevs1217 借教室
借教室(classroom.cpp/c/pas)[问题描述]在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室 ...
- 分享知识-快乐自己:大数据(hadoop)环境搭建
大数据 hadoop 环境搭建: 一):大数据(hadoop)初始化环境搭建 二):大数据(hadoop)环境搭建 三):运行wordcount案例 四):揭秘HDFS 五):揭秘MapReduce ...
- Redis-benchmark使用总结
Redis-benchmark为Redis性能测试工具. 指令说明: Usage: redis-benchmark [-h <host>] [-p <port>] [-c &l ...
- jQuery Cloud Zoom:图片放大镜插件 破解插件
/* Cloud Zoom 10 Site License (CZ01-10). Version 3.1 rev 1312051822 */ (function(e) { function s(a) ...
- 实现stack 加上·getMin功能 时间复杂度为O(n)
package com.hzins.suanfa; import java.util.Stack; /** * 实现stack 加上·getMin功能 时间复杂度为O(n) * @author Adm ...
- Prototype Chain
参考资料: <javascript权威指南 第六版> <javascript高级程序设计 第二版> 写在前面的话 所谓的原型链就是一个一个的对象通过其__proto__属性连接 ...
- 《java编程思想》:散列的原理
以实现一个简单的HashMap为例,详细讲解在code之中. 简单解释散列原理: 1.map中内建固定大小数组,但是数组并不保存key值本身,而是保存标识key的信息 2.通过key生成数组角标,对应 ...
- Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
使用hibernate的时候,报出这个错误Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvir ...
- FFmpeg 'scale' filter not present, cannot convert pixel formats.
/*************************************************************************** * FFmpeg 'scale' filter ...
- FFmpeg基础知识之————H264编码profile & level控制
H.264有四种画质级别,分别是baseline, extended, main, high: 1.Baseline Profile:基本画质.支持I/P 帧,只支持无交错(Progressive)和 ...