goim中的 bilibili/discovery (eureka)基本概念及应用

https://juejin.im/post/5cc10b086fb9a0323c526bb0

goim 文章系列(共5篇):

有个 slack 频道, 不少朋友在交流 goim , 欢迎加入slack #goim

0. 背景与动机

在学习 goim 过程中, bilibili/discovery 是一个服务注册/发现的依赖网元, golang 实现了 netflix/eureka 并作了一些扩展改进

这里顺带记录了对 bilibili/discovery 学习过程中的一些理解

1. discovery 在goim 中的角色与作用






上图标示了 bilibili/discoverygoim 中的位置, 与作用(以 comet / job 为例):






  • 部署一到多分 discovery 作为服务注册/发现网元 ( discovery 相互间会同步注册数据,细节见后)
  • comet 一到多个部署, 这里是一个 comet gRPC server 服务端
    • comet 启动果, 每一个部署向 discovery 进行—> 服务注册
    • 注册成功后与 discovery 之间保持一个健康状态同步( renew ), 见标示 1
    • comet 如果下线, discovery 会标示下线状态
  • job 一到多个部署, 这里是一个 comet gRPC client 客户端
    • job 启动后, 向 discovery 进行 polls 获取 goim-comet 所有服务实例列表—> 服务发现
    • job 持续监听 discovery 中的 goim-conet 服务节点列表, 同步到本地
    • job 向 goim-comet 实例( 整个列表) 分发 goim 消息 —-> job 的主体业务功能

如果 discovery 网元不存在, 那很简单, job 在配置文件中写死 comet 地址( 一到多个), job 的 comet-gRPC-client 直接向 comet 的 comet-gRPC-server 进行互通完成业务. 这样就失去了分布式的动态扩展能力


discovery 之间, 会同步注册的服务实例信息

注意 在 bilibili/discovery 中, discovery 本身被标记为

  1. _appid = "infra.discovery"

相互之间一样进行相互注册/更新, 同在相互之间同步 名为”infra.discovery” 与其他 app 的实例信息

在 discovery 的配置文件中, discovery 实例被称为 node , 由 nodes 参数进行配置, 配置定义如下

  1. // Config discovery configures.
  2. type Config struct {
  3. Nodes []string # ******************** 这是配置一到多个 discovery 实例的定义
  4. Region string
  5. Zone string
  6. Env string
  7. Host string
  8. }

2. discovery / eureka 的基本概念

2.1 基本概念






discovery / eureka 中的基本概念, 如上图所示, 就是一个分区进行注册/调度的简单划分

  • Region 地区, 例如, 中国区, 南美区, 北美区…
  • Zone 可用区域, 例如中国区下的 gd 广东地区, sh 上海地区, 一般是指骨干 IDC 机房, 或者跨地区的逻辑区域, 这是同区内调度的主要划分点. 一般是同区内调度, 不会跨区调度
  • Env 再划分小一点的运行环境划分, 比如 Env = dev 开发环境, Env = trial 试商用…
  • appID 这是注册应用的名称, 服务注册与发现, 依赖的是 name ——> address 名称到地址的注册(写入/更新) 与发现( 获取名称对应的服务地址或服务地址列表)

: bilibili/discovery 是以 http 方式提供注册/更新/发现/同步…等服务注册与发现等业务功能

所以, 可以看到 discorevy 获取一个服务器节点, 是如下方式

  1. curl 'http://127.0.0.1:7171/discovery/fetch?zone=gd&env=dev&appid=goim.comet&status=1'

上面 URL 中, zone 对应就是获取 gd 广东区域内, 环境定义为 dev , appID 为 goim.comet 的服务器实例, 当然, status 是附加约束, 这里 status=1 表示过滤名称为 goim.comet 的服务器实例状态要求为 status = 1 ( 即接收服务请求的 goim.comet 实例列表)

上面的 curl 会返回以下结果

  1. {
  2. "code": 0,
  3. "data": {
  4. "instances": {
  5. "gd": [
  6. {
  7. "zone": "gd", # *** ** 可用区域
  8. "env": "dev", # ****** 运行环境
  9. "appid": "goim.comet", # ****** appID 名称
  10. "hostname": "hostname000000",
  11. "version": "111",
  12. "metadata": {
  13. "provider": "",
  14. "weight": "10"
  15. },
  16. "addrs": [
  17. "http://172.1.1.1:8080",
  18. "gorpc://172.1.1.1:8089" # ****** 有效的 gRPC 地址
  19. ],
  20. "status": 1,
  21. "reg_timestamp": 1525948301833084700,
  22. "up_timestamp": 1525948301833084700,
  23. "renew_timestamp": 1525949202959821300,
  24. "dirty_timestamp": 1525948301848680000,
  25. "latest_timestamp": 1525948301833084700
  26. }
  27. ]
  28. },
  29. "latest_timestamp": 1525948301833084700
  30. }
  31. }

discovery/ eureka 换成 DNS 域名 可以在逻辑上表示为 schema://appID.Env.Zone.Region , 类似于 grpc://goim.comet.dev.gd.china.xxxxx.com

换成 etcd 可以表示为 /Region/Zone/Env/appID, 例如 “/china/gd/dev/goim.comet”

2.2 小结与配置建议

由上小节可知, bilibili/discovery 或 netflix/eureka 的配置中, 以下4个关键参数, 需要一一对应

  • region
  • zone
  • env 或 deployEnv
  • appID

在 goim 中, appID 已经在代码中标记为常量, 如下

  1. # github.com/Terry-Mao/goim/cmd/comet/main.go
  2. const (
  3. ver = "2.0.0"
  4. appid = "goim.comet"
  5. )
  6. # github.com/Terry-Mao/goim/cmd/logic/main.go
  7. const (
  8. ver = "2.0.0"
  9. appid = "goim.logic"
  10. )

3. goim 中使用 bilibili/discovery

还是以 logic / comet 之间的 gRPC 为例

所有使用 bilibili/discovery 的配置是类似的, 在配置中, 包含以下定义

原始定义在 github.com/bilibili/di… 第 46行开始

  1. // Config discovery configures.
  2. type Config struct {
  3. Nodes []string # ******************** 这是配置一到多个 discovery 实例的定义
  4. Region string
  5. Zone string
  6. Env string
  7. Host string
  8. }

在 comet 配置中定义为

在 comet 配置源文件中 github.com/Terry-Mao/g… 第 112 行

  1. // Config is comet config.
  2. type Config struct {
  3. Debug bool
  4. Env *Env # ******************** 这里这里这里
  5. Discovery *naming.Config # ******************** 这里这里这里
  6. TCP *TCP
  7. Websocket *Websocket
  8. Protocol *Protocol
  9. Bucket *Bucket
  10. RPCClient *RPCClient
  11. RPCServer *RPCServer
  12. Whitelist *Whitelist
  13. }

在 job 配置源文件中 github.com/Terry-Mao/g… 第 59 行

  1. // Config is job config.
  2. type Config struct {
  3. Env *Env # ******************** 这里这里这里
  4. Kafka *Kafka
  5. Discovery *naming.Config # ******************** 这里这里这里
  6. Comet *Comet
  7. Room *Room
  8. }

就像第二节所说的, regoin / zone / env , 所以, 重点关注 Env / Discovery 两个配置定义, 重点在 Discovery 配置naming.Config 即可

3.1 在 comet 中的服务注册, 与服务更新

3.1.1 注册如下

源代码见 github.com/Terry-Mao/g… 第42/43 行

  1. // register discovery
  2. dis := naming.New(conf.Conf.Discovery)
  3. resolver.Register(dis)

3.1.2 更新如下

该 comet 的注册信息更新代码放在一个 goroutine 中, 每10秒更新一次

源代码见 github.com/Terry-Mao/g… 第42/43 行

  1. if err = dis.Set(ins); err != nil {
  2. log.Errorf("dis.Set(%+v) error(%v)", ins, err)
  3. time.Sleep(time.Second)
  4. continue
  5. }
  6. time.Sleep(time.Second * 10)

3.2 在 job 中的服务发现

3.2.1 job 中的注册代码, 实际是无用代码

在 job 代码中, 含有服务注册代码, 实际上是无用代码, 原因是, 只有服务端才需要进行服务注册, 而 job 实际上只有两个业务关联逻辑

  1. 对 kafka 进行消息订阅
  2. 向 tomet 中的 comet gRPC server 进行消息 push 推送

代码在 github.com/Terry-Mao/g… 第 28行

  1. // grpc register naming
  2. dis := naming.New(conf.Conf.Discovery)
  3. resolver.Register(dis)

3.2.2 job 中的服务发现代码

代码在 github.com/Terry-Mao/g… 第 85行

  1. func (j *Job) watchComet(c *naming.Config) {
  2. dis := naming.New(c) # **************************** 构造符合 gRPC 要求的服务发现实例
  3. resolver := dis.Build("goim.comet")
  4. event := resolver.Watch() # **************************** 监听 服务发现, 这里返回一个 channel
  5. select { # **************************** 从 channel 中循环获取返回
  6. case _, ok := <-event:
  7. if !ok {
  8. panic("watchComet init failed")
  9. }
  10. if ins, ok := resolver.Fetch(); ok { # **************************** ins 即是返回的实例
  11. if err := j.newAddress(ins.Instances); err != nil {
  12. panic(err)
  13. }
  14. log.Infof("watchComet init newAddress:%+v", ins)
  15. }
  16. case <-time.After(10 * time.Second):
  17. log.Error("watchComet init instances timeout")
  18. }
  19. go func() {
  20. for {
  21. if _, ok := <-event; !ok {
  22. log.Info("watchComet exit")
  23. return
  24. }
  25. ins, ok := resolver.Fetch() # **************************** ins 即是返回的实例
  26. if ok {
  27. if err := j.newAddress(ins.Instances); err != nil {
  28. log.Errorf("watchComet newAddress(%+v) error(%+v)", ins, err)
  29. continue
  30. }
  31. log.Infof("watchComet change newAddress:%+v", ins)
  32. }
  33. }
  34. }()
  35. }

4. bilibili/discovery 架构与实现简要解读

………….稍后一一道来, 哈, 先去挣点钱先………….

.

5. 一些必读的好文章

  1. 详解 Eureka 缓存机制 — 文章作者: 冯永彪

.

欢迎交流与批评….. .

.

关于我

网名 tsingson (三明智, 江湖人称3爷)

原 ustarcom IPTV/OTT 事业部播控产品线技术架构湿/解决方案工程湿角色(8年), 自由职业者,

喜欢音乐(口琴,是第三/四/五届广东国际口琴嘉年华的主策划人之一), 摄影与越野,

喜欢 golang 语言 (商用项目中主要用 postgres + golang )

tsingson 写于中国深圳 小罗号口琴音乐中心, 2019/04/25

ft_authoradmin  ft_create_time2019-08-03 16:19
 ft_update_time2019-08-03 16:19