关于redis:Codis源码分析之环境篇

46次阅读

共计 6555 个字符,预计需要花费 17 分钟才能阅读完成。

一、Codis 介绍

Codis 是豌豆荚开源的 Redis 集群计划,github 地址:

https://github.com/CodisLabs/…

以下是官网介绍:

Codis 是一个分布式 Redis 解决方案, 对于下层的利用来说, 连贯到 Codis Proxy 和连贯原生的 Redis Server 没有显著区别 (不反对的命令列表), 下层利用能够像应用单机的 Redis 一样应用, Codis 底层会解决申请的转发, 不停机的数据迁徙等工作, 所有后边的所有事件, 对于后面的客户端来说是通明的, 能够简略地认为后边连贯的是一个内存无限大的 Redis 服务。

https://github.com/CodisLabs/…

Codis 次要解决的是 redis 的扩大和运维问题,因为 redis 官网以前没有集群计划,自从 3.0 才有,并且刚开始做的比拟弱,特地是运维这块不是很敌对,很多都是命令行操作的。咱们能够认为 Codis 是一个能够反对容量能够有限扩充的 Redis 集群就行。

二、Codis 装置及配置

1、整体架构

Codis 是由 golang 写的,因而须要先装置 go 语言,官网给的文档曾经很具体了,这里不详述。

咱们看下 Codis 的整体架构,基于 3.2:

2、相干概念

Codis Server:能够了解为原生的 Redis,在整个集群中充当存储用,即最终数据是存在 Redis 中的,不过 Codis 在下面改了些货色,加了些命令,便于集群迁徙和分片;

Codis Proxy:客户端连贯的 Redis 代理服务, 实现了 Redis 协定。即 Proxy 只是一个代理,把命令做解析,而后依据路由规定转到不同的 Codis Server 中,Proxy 是无状态的,能够有限扩容,Proxy 信息保留在 Zookeeper 等 Storage(前面再介绍)中,客户端须要本人刷新最新的 Proxy 列表。

Codis Dashboard:这个是 Codis 的外围,对集群的绝大部分批改都是它来实现的。官网的介绍如下:集群管理工具,反对 codis-proxy、codis-server 的增加、删除,以及据迁徙等操作;说的比拟抽象,上面咱们会通过代码来阐明其作用。

Codis FE:集群治理界面,能够了解为 UI。

Codis Admin:集群治理的命令行工具,可用于管制 codis-proxy、codis-dashboard 状态以及拜访内部存储。为什么有了 Dashboard,还要 Codis Admin,这个工具的次要定位是通过命令行的形式操作,其实前面的逻辑是一样的,即 Codis Admin 和 Codis Fe 是两种不同的界面,最初的逻辑是一样的。

Storage:次要存储元数据,如集群有多少个 Proxy,以后的分片是怎么样的。目前有 Zookeeper、Etcd、Fs 等形式的实现。

3、集群启动

整个集群的启动形式官网也有阐明,大体程序如下:

1)、启动 codis-dashboard

/admin/codis-dashboard-admin.sh start

最终启动的是 codis-dashboard 这个二进制文件:

./bin/codis-dashboard --ncpu=4 --config=dashboard.toml     --log=dashboard.log --log-level=WARN

次要的参数是 –config,即配置文件,外围几个配置如下:

coordinator_name = "zookeeper"
coordinator_addr = "127.0.0.1:2181"

# Set Codis Product {Name/Auth}.
product_name = "codis-demo"
product_auth = ""

# Set bind address for admin(rpc), tcp only.
admin_addr = "0.0.0.0:18080"

coordinator_name 和 coordinator_addr 配置的是 Storage 的类型及地址;

product_name 配置的是集群的名称,Codis 以 Product 作为一个集群,每个集群有不同的 Codis Server 和 Codis Proxy 及 Dashboard;product_auth 为明码,如果开发环境倡议留空,生产上最好是不为空。

2)、启动 codis-proxy

./admin/codis-proxy-admin.sh start

最终启动的是 codis-proxy 这个二进制文件

./bin/codis-proxy --ncpu=4 --config=proxy.toml     --log=proxy.log --log-level=WARN &

最次要的是 –config 参数,外围参数有:

product_name = "codis-demo"
product_auth = ""

# Set bind address for admin(rpc), tcp only.
admin_addr = "0.0.0.0:11080"

product_name 和 product_auth 要和下面的 Codis Dashboard 对应上

3)启动 codis-server

./admin/codis-server-admin.sh start

4)启动 codis-fe

./admin/codis-fe-admin.sh star

最终启动的是 codis-fe 这个二进制文件

./bin/codis-fe --ncpu=4 --log=fe.log --log-level=WARN     --zookeeper=127.0.0.1:2181 --listen=127.0.0.1:8080 &

listen 参数比拟重要,就是咱们要在浏览器拜访的地址了。

Codis Admin 是命令行不必启动,每次执行一个命令就退出了。

三、代码剖析各组件作用

后面讲到了 Codis 的和组件,总体还是比拟多的,我刚开始也是一头雾水,通过看代码就比拟清晰了。

咱们先看下 Codis 整体代码构造:

代码大略在以下目录:

cmd:下面说的二进制文件的入口,即 main 函数所在文件,这个上面不同命令有不同的文件夹,都叫 main.go。

pkg:外围的业务逻辑

vendor:次要是寄存第三方代码库

1)、Codis Dashboard

 s, err := topom.New(client, config)
  if err != nil {log.PanicErrorf(err, "create topom with config file failedn%s", config)
  }
  
  // 省略一些代码
  for i := 0; !s.IsClosed() && !s.IsOnline(); i++ {
    // 启动 Topom
    if err := s.Start(true); err != nil {
      if i <= 15 {log.Warnf("[%p] dashboard online failed [%d]", s, i)
      } else {log.Panicf("dashboard online failed, give up & abort :'(")
      }
      time.Sleep(time.Second * 2)
    }
  }

dashboard 的初始化逻辑都在下面了,几个要害代码是 topom.New 以及 s.Start(true),Start 的逻辑当前再细讲,明天关注 topom 的初始化。

topom.new 会启动一个协程执行:

s.serveAdmin()

这个函数外部会启动一个 apiServer,以下是具体的路由:

r.Get("/", func(r render.Render) {r.Redirect("/topom")
  })
  r.Any("/debug/**", func(w http.ResponseWriter, req *http.Request) {http.DefaultServeMux.ServeHTTP(w, req)
  })

  r.Group("/topom", 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/topom", func(r martini.Router) {r.Get("/model", api.Model)
    r.Get("/xping/:xauth", api.XPing)
    r.Get("/stats/:xauth", api.Stats)
    r.Get("/slots/:xauth", api.Slots)
    r.Put("/reload/:xauth", api.Reload)
    r.Put("/shutdown/:xauth", api.Shutdown)
    r.Put("/loglevel/:xauth/:value", api.LogLevel)
    
    r.Group("/proxy", func(r martini.Router) {r.Put("/create/:xauth/:addr", api.CreateProxy)
      r.Put("/online/:xauth/:addr", api.OnlineProxy)
      r.Put("/reinit/:xauth/:token", api.ReinitProxy)
      r.Put("/remove/:xauth/:token/:force", api.RemoveProxy)
    })
    r.Group("/group", func(r martini.Router) {r.Put("/create/:xauth/:gid", api.CreateGroup)
      r.Put("/remove/:xauth/:gid", api.RemoveGroup)
      r.Put("/resync/:xauth/:gid", api.ResyncGroup)
      r.Put("/resync-all/:xauth", api.ResyncGroupAll)
      r.Put("/add/:xauth/:gid/:addr", api.GroupAddServer)
      r.Put("/add/:xauth/:gid/:addr/:datacenter", api.GroupAddServer)
      r.Put("/del/:xauth/:gid/:addr", api.GroupDelServer)
      r.Put("/promote/:xauth/:gid/:addr", api.GroupPromoteServer)
      r.Put("/replica-groups/:xauth/:gid/:addr/:value", api.EnableReplicaGroups)
      r.Put("/replica-groups-all/:xauth/:value", api.EnableReplicaGroupsAll)
      r.Group("/action", func(r martini.Router) {r.Put("/create/:xauth/:addr", api.SyncCreateAction)
        r.Put("/remove/:xauth/:addr", api.SyncRemoveAction)
      })
      r.Get("/info/:addr", api.InfoServer)
    })
    r.Group("/slots", func(r martini.Router) {r.Group("/action", func(r martini.Router) {r.Put("/create/:xauth/:sid/:gid", api.SlotCreateAction)
        r.Put("/create-some/:xauth/:src/:dst/:num", api.SlotCreateActionSome)
        r.Put("/create-range/:xauth/:beg/:end/:gid", api.SlotCreateActionRange)
        r.Put("/remove/:xauth/:sid", api.SlotRemoveAction)
        r.Put("/interval/:xauth/:value", api.SetSlotActionInterval)
        r.Put("/disabled/:xauth/:value", api.SetSlotActionDisabled)
      })
      r.Put("/assign/:xauth", binding.Json([]*models.SlotMapping{}), api.SlotsAssignGroup)
      r.Put("/assign/:xauth/offline", binding.Json([]*models.SlotMapping{}), api.SlotsAssignOffline)
      r.Put("/rebalance/:xauth/:confirm", api.SlotsRebalance)
    })
    r.Group("/sentinels", func(r martini.Router) {r.Put("/add/:xauth/:addr", api.AddSentinel)
      r.Put("/del/:xauth/:addr/:force", api.DelSentinel)
      r.Put("/resync-all/:xauth", api.ResyncSentinels)
      r.Get("/info/:addr", api.InfoSentinel)
      r.Get("/info/:addr/monitored", api.InfoSentinelMonitored)
    })
  })

能够看到 dashboard 有以下几块性能:

proxy 治理:即 /proxy 结尾的申请

group 治理及 slots 治理,还有一些统计信息。

2)、Codis Fe

入口为 cmd/fe 上面的 main.go,外围就启动 2 个路由解决:

r.Get("/list", func() (int, string) {names := router.GetNames()
    sort.Sort(sort.StringSlice(names))
    return rpc.ApiResponseJson(names)
  })

  r.Any("/**", func(w http.ResponseWriter, req *http.Request) {name := req.URL.Query().Get("forward")
    if p := router.GetProxy(name); p != nil {p.ServeHTTP(w, req)
    } else {w.WriteHeader(http.StatusForbidden)
    }
  })

其中 /list 申请是进入 Codis Fe 主界面就会调用的,用来展现以后有多少集群的;

还有一个路由是转发的,即如果参数中有 forward 参数,会把申请转到相应的 Proxy 来解决。

能够看到 Proxy 就两性能:一是显示所有 Product Name,即以后有多少集群,第二就是申请转发,行将申请转到具体的 Proxy。

3)、Codis Proxy

入口文件在 cmd/proxy/main.go,

s, err := proxy.New(config)

proxy.New 会启动几个协程来解决工作:

go s.serveAdmin()
  go s.serveProxy()
  // 上面是一些统计信息
  s.startMetricsJson()
  s.startMetricsInfluxdb()
  s.startMetricsStatsd()

重点关注后面 2 个,特地是第 2 个,s.serveProxy:

 go func(l net.Listener) (err error) {defer func() {eh <- err}()
    for {c, err := s.acceptConn(l)
      if err != nil {return err}
      NewSession(c, s.config).Start(s.router)
    }
  }(s.lproxy)

能够看到,Proxy 就是一直的 accept 连贯,而后启动一个 Session。

总结下:Proxy 的作用是解决客户端的连贯,而后为每个连贯建设一个 Session,再依据客户端具体输出的命令进行解决,具体细节后续再聊。

正文完
 0