Codis源码解析——proxy的启动
来源:互联网 发布:淘宝无线端链接 编辑:程序博客网 时间:2024/05/22 09:38
proxy启动的时候,首先检查输入的命令行,一般情况下,启动proxy的命令如下:
nohup ./bin/codis-proxy --ncpu=2 --config=./conf/proxy.conf --log=./logs/proxy.log --log-level=WARN &
程序会解析这行命令参数,下面举个例子(实例代码是cmd/proxy/main.go),有关于go的并行,这里要特别说明一下,如果不显示的指明GOMAXPROCS的话,goroutine都是运行在同一个CPU核心上,一个goroutine得到时间片的时候,其他的goroutine都在等待。所以还是要根据输入手动指定一下的。
d, err := docopt.Parse(usage, nil, true, "", false)var ncpu intif n, ok := utils.ArgumentInteger(d, "--ncpu"); ok { ncpu = n} else { ncpu = 4}runtime.GOMAXPROCS(ncpu)
config制定了配置文件的目录,程序会读取配置文件中的配置项,填充到pkg/proxy/config.go中的config struct中。
当所有config属性填充完毕之后,调用pkg/proxy/proxy.go中的构造方法,得到一个Proxy,首先我们看看proxy这个struct的结构
type Proxy struct { mu sync.Mutex xauth string //不要搞混了,这个是/pkg/models/proxy.go下面的Proxy struct,也就是proxy成功创建之后, //在zookeeper的/codis3/codis-wujiang/proxy/proxy-token中展示的信息 model *models.Proxy //接收退出信息的channel exit struct { C chan struct{} } online bool closed bool config *Config //Router中存储了集群中所有sharedBackendConnPool和slot,用于将redis请求转发给相应的slot进行处理 router *Router ignore []byte //监听proxy的19000端口的Listener,也就是proxy实际工作的端口 lproxy net.Listener //监听proxy_admin的11080端口的Listener,也就是codis集群和proxy进行交互的端口 ladmin net.Listener ha struct { //上帝视角sentinel,并不是真正的物理服务器 //s := &Sentinel{Product: product, Auth: auth} //s.Context, s.Cancel = context.WithCancel(context.Background()) monitor *redis.Sentinel //int为groupId,标示每个group的主服务器 masters map[int]string //当前集群中的所有物理sentinel servers []string } //java客户端Jodis与codis集群交互,就是通过下面的struct,里面存储了zkClient以及"/jodis/codis-wujiang/proxy-token"这个路径 jodis *Jodis}
当然,重点还是得到proxy的这个方法。这个方法是proxy启动过程中最重要的一步,内容也很多,初次看会比较头疼
func New(config *Config) (*Proxy, error) { //config的参数校验 if err := config.Validate(); err != nil { return nil, errors.Trace(err) } if err := models.ValidateProduct(config.ProductName); err != nil { return nil, errors.Trace(err) } //新建一个Proxy,后面就是它填充属性 s := &Proxy{} s.config = config s.exit.C = make(chan struct{}) //通过config new一个Router,这一步只是初始化了Router中的两个sharedBackendConnPool的结构, //也就是map[string]*sharedBackendConn s.router = NewRouter(config) s.ignore = make([]byte, config.ProxyHeapPlaceholder.Int64()) s.model = &models.Proxy{ StartTime: time.Now().String(), } s.model.ProductName = config.ProductName s.model.DataCenter = config.ProxyDataCenter //获得当前进程的进程号 s.model.Pid = os.Getpid() //返回路径的字符串 s.model.Pwd, _ = os.Getwd() //获取当前操作系统信息和主机名 if b, err := exec.Command("uname", "-a").Output(); err != nil { log.WarnErrorf(err, "run command uname failed") } else { s.model.Sys = strings.TrimSpace(string(b)) } s.model.Hostname = utils.Hostname //将config中的参数设置到models.proxy里,主要设置的参数是admin_addr,proxy_addr,设置之后调用net.Listen进行监听,成功的话将Listener存入proxy,生成Token和xauth,以及proxy.Jodis。这里也设置了zk最终的树形路径 //如果配置文件中的jodis_compatible设置的是false,就采用codis3的zk路径,jodis/codis-demo/proxy-token。如果兼容这里设置的true,就还采用codis2时期的/zk/codis/db_productName if err := s.setup(config); err != nil { s.Close() return nil, err } //到这一步,其实proxy已经新建完毕,控制台打印出新建的proxy信息 log.Warnf("[%p] create new proxy:\n%s", s, s.model.Encode()) unsafe2.SetMaxOffheapBytes(config.ProxyMaxOffheapBytes.Int64()) //新建一个路由表,对发送到11080端口的请求做处理 go s.serveAdmin() //启动goroutine来监听发送到19000端口的redis请求 go s.serveProxy() s.startMetricsJson() s.startMetricsInfluxdb() s.startMetricsStatsd() return s, nil}
models.Proxy的详细信息会挂载在zk目录”/codis3/codis-wujiang/proxy/proxy-token”下
{ "id": 1, "token": "0317b8f67921f8c7a2d19d372cc9511b", "start_time": "2017-07-28 14:44:36.462306337 +0800 CST", "admin_addr": "*.*.*.*:11080", "proto_type": "tcp4", "proxy_addr": "*.*.*.*:19000", "jodis_path": "/jodis/codis-wujiang/proxy-0317b8f67921f8c7a2d19d372cc9511b", "product_name": "codis-wujiang", "pid": 45092, "pwd": "/app/codis", "sys": "Linux cnsz22vla888.novalocal 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux", "hostname": "cnsz22vla888.novalocal", "datacenter": ""}
路由表那里,着重介绍一下。路由表本质上其实就是一个map,key是路径 ==> “/”,value是该路径所对应的处理函数 ==> 在我们这里就是newApiServer(s)出来的处理函数。codis-proxy启动之后,这一步是启动http服务,当集群配置命令请求向prxoy_admin的11080端口发过来之后,转发做相应处理。newApiServer方法使用了这里使用了martini框架。有关于martini框架的详细信息,可以参见http://www.oschina.net/p/martini/
func (s *Proxy) serveAdmin() { if s.IsClosed() { return } defer s.Close() log.Warnf("[%p] admin start service on %s", s, s.ladmin.Addr()) eh := make(chan error, 1) go func(l net.Listener) { //新建路由表 h := http.NewServeMux() //这里表示newApiServer用来处理所有"/"路径的请求 h.Handle("/", newApiServer(s)) hs := &http.Server{Handler: h} //对每个net.Listener的连接,新建goroutine读请求,并调用srv.Handler进行处理 eh <- hs.Serve(l) }(s.ladmin) select { case <-s.exit.C: log.Warnf("[%p] admin shutdown", s) case err := <-eh: log.ErrorErrorf(err, "[%p] admin exit on error", s) }}//在/pkg/proxy/proxy_api.go中,请求转发到不同的路径,r是路由规则,最终的返回结果是由路由规则得到的handlerfunc newApiServer(p *Proxy) http.Handler { m := martini.New() m.Use(martini.Recovery()) m.Use(render.Renderer()) m.Use(func(w http.ResponseWriter, req *http.Request, c martini.Context) { path := req.URL.Path if req.Method != "GET" && strings.HasPrefix(path, "/api/") { var remoteAddr = req.RemoteAddr var headerAddr string for _, key := range []string{"X-Real-IP", "X-Forwarded-For"} { if val := req.Header.Get(key); val != "" { headerAddr = val break } } log.Warnf("[%p] API call %s from %s [%s]", p, path, remoteAddr, headerAddr) } c.Next() }) m.Use(gzip.All()) m.Use(func(c martini.Context, w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json; charset=utf-8") }) api := &apiServer{proxy: p} r := martini.NewRouter() r.Get("/", func(r render.Render) { r.Redirect("/proxy") }) r.Any("/debug/**", func(w http.ResponseWriter, req *http.Request) { http.DefaultServeMux.ServeHTTP(w, req) }) r.Group("/proxy", func(r martini.Router) { r.Get("", api.Overview) r.Get("/model", api.Model) r.Get("/stats", api.StatsNoXAuth) r.Get("/slots", api.SlotsNoXAuth) }) r.Group("/api/proxy", func(r martini.Router) { r.Get("/model", api.Model) r.Get("/xping/:xauth", api.XPing) r.Get("/stats/:xauth", api.Stats) r.Get("/stats/:xauth/:flags", api.Stats) r.Get("/slots/:xauth", api.Slots) r.Put("/start/:xauth", api.Start) r.Put("/stats/reset/:xauth", api.ResetStats) r.Put("/forcegc/:xauth", api.ForceGC) r.Put("/shutdown/:xauth", api.Shutdown) r.Put("/loglevel/:xauth/:value", api.LogLevel) r.Put("/fillslots/:xauth", binding.Json([]*models.Slot{}), api.FillSlots) r.Put("/sentinels/:xauth", binding.Json(models.Sentinel{}), api.SetSentinels) r.Put("/sentinels/:xauth/rewatch", api.RewatchSentinels) }) m.MapTo(r, (*martini.Routes)(nil)) m.Action(r.Handle) return m}
后面的serveProxy,s.acceptConn(l)是启动goroutine来监听redis请求,启动的时候肯定是没有请求过来的,所以我们看到这里为止, 下一节Codis源码解析——proxy监听redis请求会详细绍NewSession(c, s.config).Start(s.router)这里具体做了什么。
func (s *Proxy) serveProxy() { if s.IsClosed() { return } defer s.Close() log.Warnf("[%p] proxy start service on %s", s, s.lproxy.Addr()) eh := make(chan error, 1) go func(l net.Listener) (err error) { defer func() { eh <- err }() for { //启动goroutine监听19000端口请求,有请求到来的时候,返回net.Conn c, err := s.acceptConn(l) if err != nil { return err } NewSession(c, s.config).Start(s.router) } }(s.lproxy) if d := s.config.BackendPingPeriod.Duration(); d != 0 { go s.keepAlive(d) } select { case <-s.exit.C: log.Warnf("[%p] proxy shutdown", s) case err := <-eh: log.ErrorErrorf(err, "[%p] proxy exit on error", s) }}
最后面的三个方法,由于通常启动的时候,配置文件中的MetircsReporterServer,MetircsInfluxdbServer和MetircsStatsdPeriod三个字段都为空字符串,实际上什么都没做。
再回到主main函数,看看最后的一段代码
//启动时dashboard、coordinator.name和slots这三个参数都为空,所以此时这三个goroutine都没有执行switch {case dashboard != "": go AutoOnlineWithDashboard(s, dashboard)case coordinator.name != "": go AutoOnlineWithCoordinator(s, coordinator.name, coordinator.addr)case slots != nil: go AutoOnlineWithFillSlots(s, slots)}//未关闭,但也不在线的时候,控制台每秒输出日志for !s.IsClosed() && !s.IsOnline() { log.Warnf("[%p] proxy waiting online ...", s) time.Sleep(time.Second)}
到这里,整个proxy启动过程结束。启动之后,Proxy会默认处于 waiting 状态,以一秒一次的频率刷新状态,监听proxy_addr 地址(默认配置文件中的19000端口),但是不会 accept 连接,通过fe或者命令行添加到集群并完成集群状态的同步,才能改变状态为online。添加proxy到集群的过程,详见个人另一篇博客Codis源码解析——proxy添加到集群
到这里,我们总结一下,proxy启动过程中的流程:
读取配置文件,获取Config对象。根据Config新建Proxy,填充Proxy的各个属性,这里面比较重要的是填充models.Proxy(详细信息可以在zk中查看),并且与zk连接、注册相关路径。启动goroutine监听11080端口的codis集群发过来的请求并进行转发,以及监听发到19000端口的redis请求并进行相关处理。
说明
如有转载,请注明出处:
http://blog.csdn.net/antony9118/article/details/75268358
- Codis源码解析——proxy的启动
- Codis源码解析——dashboard的启动(1)
- Codis源码解析——dashboard的启动(2)
- Codis源码解析——fe的启动
- Codis源码解析——proxy监听redis请求
- Codis源码解析——proxy添加到集群
- Codis源码解析——codis-server添加到集群
- Codis源码解析——slot的分配
- Codis源码解析——sharedBackendConn
- Codis源码解析——Jodis
- Codis源码解析——处理slot操作(1)
- Codis源码解析——处理slot操作(2)
- Codis源码解析——sentinel的重同步(1)
- Codis源码解析——sentinel的重同步(2)
- codis的proxy层HA
- CODIS源码剖析二(codis-proxy功能实现)
- 豌豆夹Redis解决方案Codis源码剖析:Proxy代理
- codis proxy 配制
- iOS QQ中未读气泡拖拽消失的实现分析(KittenYang)
- 正向代理和反向代理
- 黑莓9930/9900开启电信4g网络,电信卡+3g上网
- 【时间管理】如何记高质量的笔记
- MySQL之——The slave I/O thread stops because master and slave have equal MySQL server UUIDs
- Codis源码解析——proxy的启动
- 利用链表的头插法,将链表逆序
- POJ
- python5列表
- 星悦惠购隐私政策
- LabVIEW
- 转发和重定向的区别,web路径问题
- Cloud Application学习第一课
- 打印菱形