Micro原始碼系列 - Go-Micro服務是如何註冊的
前面一章我們大體講解了 Go-Micro 中的服務是如何構建的。接下來我們就從程式碼層面給大家演示服務註冊。
微服務架構中註冊是非常有意思的角色,服務中的客戶端通過序號產生器制定位目標服務的具體位置。服務註冊中心可以說是服務例項的資料庫,在裡面有服務的各種資訊,包括位置等。服務例項在啟動時通過序號產生器制註冊到中心,並且在關閉前從中心自動解除安裝。不過光有註冊與解除安裝兩個步驟還不夠,在兩者之間,我們還需要健康檢查來確定服務是否可以持續接收請求。
同時,我們需要指出一個大家特別容易犯的錯誤:很多人都會覺得服務註冊就是為了負載均衡,其實不是,<u>服務註冊是為了客戶端或服務端定位服務例項,並確定選擇哪一個服務來傳送請求的機制</u>。而負載均衡只是選擇服務時如何讓各服務之間平衡提供響應的策略,它可能依賴註冊,但不是必須,因為有哪些服務可以通過很多方式告之客戶端,而並且非一律從註冊中心獲取。
Micro 體系中的每一種型別的服務都包含有註冊元件(Registry)。當服務啟動時,它會把所有描述自身資訊的後設資料(metadata,比如服務名、地址、transport、編碼等等)提取出來,作為關鍵資訊,用於下一步註冊成為服務節點。爾後,如果宣告有 TTL 和 Interval,則會定期觸發重序號產生器制。
註冊中心介面
註冊元件介面registry中:
package registry
// ...
type Registry interface {
Init(...Option) error
Options() Options
Register(*Service, ...RegisterOption) error
Deregister(*Service) error
GetService(string) ([]*Service, error)
ListServices() ([]*Service, error)
Watch(...WatchOption) (Watcher, error)
String() string
}
在 go-micro 包中,共有 4 種註冊實現 consul、gossip、mdns、memory,前兩個都是基於 hashicorp 公司的協議,mdns 則是基於組網廣播實現,memory 則是本地實現。
- consul 依賴 hashicorp 的元件,但是功能強大、完整
- gossip 基於 SWIM 協議廣播,零依賴
- mdns 輕量、零依賴,但是對環境有要求,某些環境不支援 mdns 的無法正常使用
- memory 本地解決方案,不可跨主機訪問
另外在go-plugins中有其它註冊實現,比如 etcd、eureka、k8s、nats、zk 等等
大體解釋下介面中每個方法的作用
- Init 初始化
- Options 獲取配置選項
- Register 註冊服務
- Deregister 解除安裝服務
- GetService 獲取指定服務
- ListServices 列出所有服務
- Watch watcher 負責偵聽變動
- String 註冊資訊轉成字串描述
可見,介面定義的註冊元件是幾乎完全自包含,它自行註冊、解除安裝、偵聽等,服務不需要關心自己如何註冊、解除安裝,只需要將註冊中心的實現作為 Option 匯入自身啟動即可
> 通過定義註冊元件介面,我們便可以將服務與註冊中心解耦
宣告註冊中心
我們知道 Go-Micro 可以通過命令列引數--registry或者方法引數micro.Registry來指定服務註冊中心,但是Register方法中並沒有選擇註冊中心的過程,我們看下 Go-Micro 在構建服務時的動作:
命令列引數:
go run main.go --registry=consul
Go-Micro 預置有 4 種命令列引數:
DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
"consul": consul.NewRegistry,
"gossip": gossip.NewRegistry,
"mdns": mdns.NewRegistry,
"memory": rmem.NewRegistry,
}
在識別命令列傳入引數後,Micro 就會匹配 DefaultRegistries 中的 key,然後把註冊元件附加給服務。
> *Env 方式大同小異,這裡不表
方法引數,通過 micro.Registry 傳入:
micReg := consul.NewRegistry(registryOptions)
service := micro.NewService(
// ...
micro.Registry(micReg),
// ...
)
因為 Registry 是自包含的,故而我們只需要將其傳入服務,讓服務呼叫即可。
服務啟動
簡單回顧下服務在Start時的動作,我們用預設的rpc_server來演示,其它如 grpc_server 等大同小異,不影響理解。
func (s *rpcServer) Start() error {
// ...
// use RegisterCheck func before register
if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
log.Logf("Server %s-%s register check error: %s", config.Name, config.Id, err)
} else {
// 註冊
if err = s.Register(); err != nil {
log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
// ...
// Interval、解除安裝程式碼,下面我們會講到
return nil
}
Start()方法在檢測完資訊後便進行註冊動作,下面我們分析註冊方法Register
Micro 服務在註冊時有兩個關鍵點,後設資料、自定義 handler
服務向中心註冊一般可以分為如下幾個步驟:
1.解析註冊中心地址
2.準備後設資料
3.宣告節點資訊
4.宣告 endpoint handlers
5.宣告服務
6.註冊
整個流程我們縮略成一個二維集合圖:
接下來我們分析一下注冊流程程式碼,大家請配合上面的集合圖閱讀,方便理解
func (s *rpcServer) Register() error {
// 解析註冊中心地址
// 忽略這部分程式碼
// 準備後設資料
md := make(metadata.Metadata)
for k, v := range config.Metadata {
md[k] = v
}
// 宣告節點資訊
node := &registry.Node{
Id: config.Name + "-" + config.Id,
Address: addr,
Port: port,
Metadata: md,
}
node.Metadata["transport"] = config.Transport.String()
node.Metadata["broker"] = config.Broker.String()
node.Metadata["server"] = s.String()
node.Metadata["registry"] = config.Registry.String()
node.Metadata["protocol"] = "mucp"
s.RLock()
// 宣告endpoint,map元素順序是隨機的,故而使用key排序,方便每個同名服務之間顯示一致
var handlerList []string
for n, e := range s.handlers {
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
}
sort.Strings(handlerList)
var endpoints []*registry.Endpoint
for _, n := range handlerList {
endpoints = append(endpoints, s.handlers[n].Endpoints()...)
}
// 忽略部分程式碼
// 宣告服務資訊
service := &registry.Service{
Name: config.Name,
Version: config.Version,
Nodes: []*registry.Node{node},
Endpoints: endpoints,
}
s.Lock()
registered := s.registered
s.Unlock()
// 構建註冊選項
rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)}
// 註冊
if err := config.Registry.Register(service, rOpts...); err != nil {
return err
}
// 忽略部分訂閱程式碼
s.registered = true
}
以上便是服務向註冊中心註冊時的主要流程程式碼,整個註冊過程非常簡單。服務註冊完後,我們還要定期檢查與宣告生存週期,也即是 Interval 與 TTL(Time-To-Live)機制。
Interval
與 Register 註冊一樣,Interval 由服務觸發,而不是由 Registry 觸發,因為 Registry 已經暴露了 Register 介面,而 Interval 的工作只是定時重新呼叫 Register 方法,如果再把 Interval 放到其中,便會導致每個 Registry 實現都會有相同的 Interval 程式碼。
我們再回顧一下上面說到的Start方法,Start 方法中除了註冊之外,還有迴圈重註冊的邏輯,這一部分就是利用 Interval 指定的值,不間斷重複向註冊中心註冊,以達到線上的目的:
func (s *rpcServer) Start() error {
// 忽略部分程式碼
go func() {
t := new(time.Ticker)
// 僅在宣告瞭Interval時才會執行,每隔Interval指定的時間,傳送一次訊號
if s.opts.RegisterInterval > time.Duration(0) {
t = time.NewTicker(s.opts.RegisterInterval)
}
// return error chan
var ch chan error
Loop:
for {
select {
// 當接收到Interval訊號時重新執行註冊操作
case <-t.C:
s.RLock()
registered := s.registered
s.RUnlock()
if err = s.opts.RegisterCheck(s.opts.Context); err != nil && registered {
log.Logf("Server %s-%s register check error: %s, deregister it", config.Name, config.Id, err)
// deregister self in case of error
if err := s.Deregister(); err != nil {
log.Logf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
} else {
if err := s.Register(); err != nil {
log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
// 直到接收到退出訊號,才停止重註冊
case ch = <-s.exit:
t.Stop()
close(exit)
break Loop
}
}
// 忽略部分解除安裝、關連線的程式碼
}()
return nil
}
> 當重註冊迴圈停止時,相當於服務不再生效,故而需要解除安裝、停止偵聽連線請求等操作。
TTL
TTL 與 Register 不同,它由註冊元件執行,並非以服務直接呼叫。故而不同的註冊中心元件有不同的實現。我們這裡不深入討論,後繼如果有機會,我們再討論每個中心的 TTL 機制。
解除安裝
服務解除安裝相當於註冊的逆過程。
func (s *rpcServer) Deregister() error {
config := s.Options()
// 忽略部分地址解析程式碼
node := &registry.Node{
Id: config.Name + "-" + config.Id,
Address: addr,
Port: port,
}
service := &registry.Service{
Name: config.Name,
Version: config.Version,
Nodes: []*registry.Node{node},
}
if err := config.Registry.Deregister(service); err != nil {
return err
}
s.Lock()
if !s.registered {
s.Unlock()
return nil
}
s.registered = false
// 忽略部分訂閱程式碼
return nil
}
解除安裝的過程很簡單,把服務名、版本號、節點資訊向註冊元件呼叫Deregister即可。
> 因為一個應用例項可能註冊多個服務,故而,我們需要將服務名傳過去,讓註冊元件停止對某個服務的偵聽工作。
總結
我們在本篇中從原始碼的角度簡單給大家介紹 Go-Micro 服務的註冊流程,不過,我們並沒有深入各註冊中心元件去詳解,這也超過本文的範疇,會讓文章變得很重,大家有興趣可以去檢視各註冊中心的客戶端程式碼。
Micro 原始碼系列
- Go-Micro 服務的構造過程
- Go-Micro 註冊解讀
- [Go-Micro 請求處理(in progress)]
Micro 中文資源
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- (6)go-micro微服務consul配置、註冊中心Go微服務
- 微服務框架 Go-Micro 整合 Nacos 實戰之服務註冊與發現微服務框架Go
- go微服務框架go-micro深度學習(三) Registry服務的註冊和發現Go微服務框架深度學習
- go-micro之原始碼剖析: RegistryGo原始碼
- go-micro講解--Go Micro編寫微服務Go微服務
- Hibernate原始碼學習四 _服務註冊原始碼
- go-micro v2運開實踐-框架篇(3)註冊第一個微服務Go框架微服務
- go微服務系列(二) - 服務註冊/服務發現Go微服務
- Laravel-admin 原始碼分析系列 1——註冊服務與安裝分析Laravel原始碼
- go-micro使用Consul做服務發現的方法和原理Go
- go-micro開發RPC服務的方法及其執行原理GoRPC
- Dubbo原始碼解析之服務釋出與註冊原始碼
- (18)go-micro微服務ELK介紹Go微服務
- Nacos(二)原始碼分析Nacos服務端註冊示例流程原始碼服務端
- [原始碼閱讀] 阿里SOFA服務註冊中心MetaServer(3)原始碼阿里Server
- Spring Cloud原始碼分析之Eureka篇第四章:服務註冊是如何發起的SpringCloud原始碼
- 微服務架構 | *3.5 Nacos 服務註冊與發現的原始碼分析微服務架構原始碼
- (7)go-micro微服務zap日誌配置Go微服務
- mysql註冊服務MySql
- spring cloud alibaba系列(一) 服務註冊SpringCloud
- 微服務Consul系列之服務註冊與發現微服務
- Spring Cloud 微服務實戰——nacos 服務註冊中心搭建(附原始碼)SpringCloud微服務原始碼
- SpringCloud元件 & 原始碼剖析:Eureka服務註冊方式流程全面分析SpringGCCloud元件原始碼
- (16)go-micro微服務jaeger鏈路追蹤Go微服務
- Laravel Providers——服務提供者的註冊與啟動原始碼解析LaravelIDE原始碼
- ElasticSearch註冊Windows服務ElasticsearchWindows
- nacos 服務註冊原理
- 【SpringBoot】服務對註冊中心的註冊時機Spring Boot
- 微服務學習小結-Eureka如何實現註冊中心,以及服務之間的註冊、呼叫微服務
- Spring Cloud 系列(一)Eureka 服務註冊與發現SpringCloud
- SpringCloud之服務註冊SpringGCCloud
- lms框架服務註冊中心框架
- 牌類遊戲使用微服務重構筆記(三): micro框架簡介 go-micro遊戲微服務筆記框架Go
- 什麼是微服務架構?什麼是服務註冊與發現微服務架構
- 微服務架構之「 服務註冊 」微服務架構
- 聊聊微服務的服務註冊與發現!微服務
- 我是服務的執政官-服務發現和註冊工具consul簡介
- Nacos 的安裝與服務的註冊