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

goim 文章系列(共5篇):node

有個 slack 頻道, 很多朋友在交流 goim , 歡迎加入slack #goimgit

0. 背景與動機

在學習 goim 過程當中, bilibili/discovery 是一個服務註冊/發現的依賴網元, golang 實現了 netflix/eureka 並做了一些擴展改進github

這裏順帶記錄了對 bilibili/discovery 學習過程當中的一些理解golang

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 進行互通完成業務. 這樣就失去了分佈式的動態擴展能力bash


discovery 之間, 會同步註冊的服務實例信息服務器

注意 在 bilibili/discovery 中, discovery 自己被標記爲架構

_appid = "infra.discovery"
複製代碼

相互之間同樣進行相互註冊/更新, 同在相互之間同步 名爲"infra.discovery" 與其餘 app 的實例信息app

在 discovery 的配置文件中, discovery 實例被稱爲 node , 由 nodes 參數進行配置, 配置定義以下curl

// Config discovery configures.
type Config struct {
	Nodes  []string  # ******************** 這是配置一到多個 discovery 實例的定義
	Region string
	Zone   string
	Env    string
	Host   string
}
複製代碼

2. discovery / eureka 的基本概念

2.1 基本概念

discovery / eureka 中的基本概念, 如上圖所示, 就是一個分區進行註冊/調度的簡單劃分

  • Region 地區, 例如, 中國區, 南美區, 北美區...
  • Zone 可用區域, 例如中國區下的 gd 廣東地區, sh 上海地區, 通常是指骨幹 IDC 機房, 或者跨地區的邏輯區域, 這是同區內調度的主要劃分點. 通常是同區內調度, 不會跨區調度
  • Env 再劃分小一點的運行環境劃分, 好比 Env = dev 開發環境, Env = trial 試商用...
  • appID 這是註冊應用的名稱, 服務註冊與發現, 依賴的是 name ----> address 名稱到地址的註冊(寫入/更新) 與發現( 獲取名稱對應的服務地址或服務地址列表)

: bilibili/discovery 是以 http 方式提供註冊/更新/發現/同步...等服務註冊與發現等業務功能

因此, 能夠看到 discorevy 獲取一個服務器節點, 是以下方式

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 會返回如下結果

{
    "code": 0,
    "data": {
        "instances": {
            "gd": [
                {
                    "zone": "gd",                # *** ** 可用區域
                    "env": "dev",                # ****** 運行環境
                    "appid": "goim.comet",       # ****** appID 名稱
                    "hostname": "hostname000000",
                    "version": "111",
                    "metadata": {
                        "provider": "",
                        "weight": "10"
                    },
                    "addrs": [
                        "http://172.1.1.1:8080",
                        "gorpc://172.1.1.1:8089" # ****** 有效的 gRPC 地址
                    ],
                    "status": 1,
                    "reg_timestamp": 1525948301833084700,
                    "up_timestamp": 1525948301833084700,
                    "renew_timestamp": 1525949202959821300,
                    "dirty_timestamp": 1525948301848680000,
                    "latest_timestamp": 1525948301833084700
                }
            ]
        },
        "latest_timestamp": 1525948301833084700
    }
}
複製代碼

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 已經在代碼中標記爲常量, 以下

# github.com/Terry-Mao/goim/cmd/comet/main.go

const (
	ver   = "2.0.0"
	appid = "goim.comet"
)

# github.com/Terry-Mao/goim/cmd/logic/main.go
const (
	ver   = "2.0.0"
	appid = "goim.logic"
)

複製代碼

3. goim 中使用 bilibili/discovery

仍是以 logic / comet 之間的 gRPC 爲例

全部使用 bilibili/discovery 的配置是相似的, 在配置中, 包含如下定義

原始定義在 github.com/bilibili/di… 第 46行開始

// Config discovery configures.
type Config struct {
	Nodes  []string  # ******************** 這是配置一到多個 discovery 實例的定義
	Region string
	Zone   string
	Env    string
	Host   string
}
複製代碼

在 comet 配置中定義爲

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

// Config is comet config.
type Config struct {
	Debug     bool
	Env       *Env            # ******************** 這裏這裏這裏
	Discovery *naming.Config  # ******************** 這裏這裏這裏
	TCP       *TCP
	Websocket *Websocket
	Protocol  *Protocol
	Bucket    *Bucket
	RPCClient *RPCClient
	RPCServer *RPCServer
	Whitelist *Whitelist
}
複製代碼

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

// Config is job config.
type Config struct {
	Env       *Env            # ******************** 這裏這裏這裏
	Kafka     *Kafka
	Discovery *naming.Config  # ******************** 這裏這裏這裏
	Comet     *Comet
	Room      *Room
}
複製代碼

就像第二節所說的, regoin / zone / env , 因此, 重點關注 Env / Discovery 兩個配置定義, 重點在 Discovery 配置naming.Config 便可

3.1 在 comet 中的服務註冊, 與服務更新

3.1.1 註冊以下

源代碼見 github.com/Terry-Mao/g… 第42/43 行

// register discovery
	dis := naming.New(conf.Conf.Discovery)
	resolver.Register(dis)
	
複製代碼

3.1.2 更新以下

該 comet 的註冊信息更新代碼放在一個 goroutine 中, 每10秒更新一次

源代碼見 github.com/Terry-Mao/g… 第42/43 行

if err = dis.Set(ins); err != nil {
				log.Errorf("dis.Set(%+v) error(%v)", ins, err)
				time.Sleep(time.Second)
				continue
			}
			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行

// grpc register naming
	dis := naming.New(conf.Conf.Discovery)
	resolver.Register(dis)
複製代碼

3.2.2 job 中的服務發現代碼

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

func (j *Job) watchComet(c *naming.Config) {
	dis := naming.New(c)       # **************************** 構造符合 gRPC 要求的服務發現實例
	resolver := dis.Build("goim.comet")
	event := resolver.Watch()  # **************************** 監聽 服務發現, 這裏返回一個 channel
	select {                   # **************************** 從 channel 中循環獲取返回
	case _, ok := <-event:
		if !ok {
			panic("watchComet init failed")
		}
		if ins, ok := resolver.Fetch(); ok {   # **************************** ins 便是返回的實例
			if err := j.newAddress(ins.Instances); err != nil {
				panic(err)
			}
			log.Infof("watchComet init newAddress:%+v", ins)
		}
	case <-time.After(10 * time.Second):
		log.Error("watchComet init instances timeout")
	}
	go func() {
		for {
			if _, ok := <-event; !ok {
				log.Info("watchComet exit")
				return
			}
			ins, ok := resolver.Fetch()     # **************************** ins 便是返回的實例
			if ok {
				if err := j.newAddress(ins.Instances); err != nil {
					log.Errorf("watchComet newAddress(%+v) error(%+v)", ins, err)
					continue
				}
				log.Infof("watchComet change newAddress:%+v", ins)
			}
		}
	}()
}
複製代碼

4. bilibili/discovery 架構與實現簡要解讀

.............稍後一一道來, 哈, 先去掙點錢先.............

.

5. 一些必讀的好文章

  1. 詳解 Eureka 緩存機制 -- 文章做者: 馮永彪

.

歡迎交流與批評..... .

.

關於我

網名 tsingson (三明智, 江湖人稱3爺)

原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色(8年), 自由職業者,

喜歡音樂(口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,

喜歡 golang 語言 (商用項目中主要用 postgres + golang )

tsingson 寫於中國深圳 小羅號口琴音樂中心, 2019/04/25

相關文章
相關標籤/搜索