goim 文章系列:
0. 背景與動機
在學習 goim 過程中, bilibili/discovery 是一個服務註冊/發現的依賴網元, golang 實現了 netflix/eureka 並作了一些擴充套件改進
這裡順帶記錄了對 bilibili/discovery 學習過程中的一些理解
1. discovery 在goim 中的角色與作用
上圖示示了 bilibili/discovery 在 goim 中的位置, 與作用(以 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 本身被標記為
_appid = "infra.discovery"
複製程式碼
相互之間一樣進行相互註冊/更新, 同在相互之間同步 名為"infra.discovery" 與其他 app 的例項資訊
在 discovery 的配置檔案中, discovery 例項被稱為 node , 由 nodes 引數進行配置, 配置定義如下
// 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 實際上只有兩個業務關聯邏輯
- 對 kafka 進行訊息訂閱
- 向 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 架構與實現簡要解讀
.............稍後一一道來, 哈, 先去掙點錢先.............
.
.
.
歡迎交流與批評..... .
.
關於我
網名 tsingson (三明智, 江湖人稱3爺)
原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色(8年), 自由職業者,
喜歡音樂(口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,
喜歡 golang 語言 (商用專案中主要用 postgres + golang )