豌豆夹Redis解决方案Codis源码剖析:Proxy代理
1.预备知识
1.1 Codis
Codis就不详细说了,摘抄一下GitHub上的一些项目描述:
Codis is a proxy based high performance Redis cluster solution written in Go/C, an alternative to Twemproxy. It supports multiple stateless proxy with multiple redis instances and is engineered to elastically scale, Easily add or remove redis or proxy instances on-demand/dynamicly.
- Auto rebalance
- Support both redis or rocksdb transparently
- GUI dashboard & admin tools
- Supports most of Redis commands, Fully compatible with twemproxy
- Native Redis clients are supported
- Safe and transparent data migration, Easily add or remove nodes on-demand
- Command-line interface is also provided
- RESTful APIs
安装步骤官方网站上也写的很清楚了:
// Golang环境安装配置
[root@vm root]$ tar -C /usr/local -zxf go1.4.2.linux-amd64.tar.gz
[root@vm root]$ vim /etc/profile
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/user/go
[root@vm root]$ source /etc/profile
[root@vm root]$ go version
// 下载Codis依赖,编译Codis
[root@vm root]$ cd codis-1.92
[root@vm root]$ ./bootstrap.sh
1.2 Golang
Codis的核心代码都是用Golang开发的,所以在一头扎进源代码里之前,先了解Golang的语法特性是必不可少的!好在Golang除了少数一些“古怪之处”外,还算容易上手。具体请参考笔者的另一篇文章《 Java程序员的Golang入门指南(上)》。
1.3 Redis通信协议
Redis通信协议简称为RESP,在分析网络通信时需要这一部分的知识。RESP本身的设计非常简单,所以还是快速过一下吧。具体请参考笔者的另一篇文章《用Netty解析Redis网络协议》以及官网上的协议具体规范。
1.4 Zookeeper
Codis以及现今很多后端中间件都使用Zookeeper来协调分布式通信,所以在阅读源码前我们至少要知道Zookeeper是干什么的,有哪些基本操作和监听器。具体请参考笔者的另一篇文章《Apache Curator入门实战》。
2.Proxy源码剖析
Codis可以分为客户端Jodis、代理中间件Codis Proxy、Zookeeper协调、监控界面、Redis定制版Codis Server等组件。这里第一部分主要关注最核心的Proxy部分的源码。
2.1 程序入口main.go
codis-1.92/cmd/proxy/main.go是Proxy组件的main函数入口,完成的主要工作就是设置日志级别、解析命令行参数(CPU核数、绑定地址等)、加载配置文件、Golang环境(runtime.GOMAXPROCS并发数)、启动Socket监听等常规任务。顺藤摸瓜,我们要分析的关键应该就在router中。
func main() {
// 1.打印banner,设置日志级别
fmt.Print(banner)
log.SetLevelByString("info")
// 2.解析命令行参数
args, err := docopt.Parse(usage, nil, true, "codis proxy v0.1", true)
if err != nil {
log.Error(err)
}
if args["-c"] != nil {
configFile = args["-c"].(string)
}
...
dumppath := utils.GetExecutorPath()
log.Info("dump file path:", dumppath)
log.CrashLog(path.Join(dumppath, "codis-proxy.dump"))
// 3.设置Golang并发数等
router.CheckUlimit(1024)
runtime.GOMAXPROCS(cpus)
// 4.启动Http监听
http.HandleFunc("/setloglevel", handleSetLogLevel)
go http.ListenAndServe(httpAddr, nil)
log.Info("running on ", addr)
conf, err := router.LoadConf(configFile)
if err != nil {
log.Fatal(err)
}
// 5.创建Server,启动Socket监听
s := router.NewServer(addr, httpAddr, conf)
s.Run()
log.Warning("exit")
}
2.2 核心类Server
打开codis-1.92/pkg/proxy/router/router.go,在分析请求接收和分发前,先来看一个最核心的类Server,它就是在main.go中调用router.NewServer()时创建的。说一下比较重要的几个字段:
- reqCh:Pipeline请求的Channel。
- pools:Slot与cachepool的map。
- evtbus/top:处理Zookeeper消息,更新拓扑结构。
- bufferedReq:Slot处于migrate期间被缓冲的请求。
- pipeConns:Slot对应的taskrunner。
注意interface{},它表示空interface,按照Golang的Duck Type继承方式,任何类都是空接口的子类。所以interface{}有点像C语言中的void*/char*。
因为Codis是先启动监听再开始接收Socket请求,所以对go s.handleTopoEvent()的分析放到后面。在下一节我们先看一下Codis是如何启动对Socket端口监听并将接收到的请求放入到Server的reqCh管道中的。
type Server struct {
slots [models.DEFAULT_SLOT_NUM]*Slot
top *topo.Topology
evtbus chan interface{}
reqCh chan *PipelineRequest
lastActionSeq int
pi models.ProxyInfo
startAt time.Time
addr string
moper *MultiOperator
pools *cachepool.CachePool
counter *stats.Counters
OnSuicide OnSuicideFun
bufferedReq *list.List
conf *Conf
pipeConns map[string]*taskRunner //redis->taskrunner
}
func NewServer(addr string, debugVarAddr string, conf *Conf) *Server {
log.Infof("start with configuration: %+v", conf)
// 1.创建Server类
s := &Server{
conf: conf,
evtbus: make(chan interface{}, 1000),
top: topo.NewTopo(conf.productName, conf.zkAddr, conf.f, conf.provider),
counter: stats.NewCounters("router"),
lastActionSeq: -1,
startAt: time.Now(),
addr: addr,
moper: NewMultiOperator(addr),
reqCh: make(chan *PipelineRequest, 1000),
pools: cachepool.NewCachePool(),
pipeConns: make(map[string]*taskRunner),
bufferedReq: list.New(),
}
...
// 2.启动Zookeeper监听器
s.RegisterAndWait()
_, err = s.top.WatchChildren(models.GetWatchActionPath(conf.productName),