f12控制台如何查看consul_基于 Consul 的 Go Micro 客户端服务发现是如何实现的
基于 Consul 的 Go Micro 客戶端服務發現是如何實現的
由 學院君 創建于1年前, 最后更新于 1年前
版本號 #1
上篇分享我們介紹了基于 Consul 作為注冊中心的 Go Micro 服務注冊底層實現原理,今天我們來看看 Go Micro 中客戶端服務發現是如何實現的。
客戶端服務發現要復雜一些,涉及到服務發現 Registry 和節點選擇 Selector 兩部分。
所謂服務發現指的是當我們從客戶端向指定服務發起請求時,可以通過名字識別服務,然后通過服務發現獲取到包含 IP 地址和端口號的對應遠程服務實例,遠程服務會在啟動時向注冊中心注冊,退出時注銷,客戶端無需關心這些細節,由 Go Micro 的 Registry 組件統一處理服務注冊與發現邏輯(這里,我們基于 Consul 作為 Registry 的具體實現插件)。
而節點選擇指的是,遠程服務實例通常部署在多個節點上,通過指定的服務名稱可以獲取到一個地址列表,節點選擇要做的事情是通過某種策略從列表中獲取指定的 IP 進行訪問,這就是 Go Micro 中 Selector 組件發揮作用的地方,它基于 Registry 組件實現,提供了負載均衡策略,比如輪詢或隨機,以及過濾、緩存和黑名單的功能。
下面我們還是通過分析客戶端調用底層源碼來看下 Go Micro 框架中服務發現與節點選擇的具體實現。首先打開 ~/go/hello/src/hello/client.go 文件,在 main 函數中,通過一系列的初始化操作后,真正發起服務調用的代碼是 greeter.Hello 函數調用:
func main() {
// Create a new service. Optionally include some options here.
service := micro.NewService(micro.Name("go.micro.cli.greeter"))
service.Init()
// Create new greeter client
greeter := proto.NewGreeterService("go.micro.srv.greeter", service.Client())
// Call the greeter
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "學院君"})
if err != nil {
fmt.Println(err)
}
// Print response
fmt.Println(rsp.Greeting)
}
greeter.Hello 函數定義在 ~/go/hello/src/hello/proto/hello.micro.go 中:
func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.name, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
在這個函數中,通過 NewRequest 初始化了請求實例,包含服務名稱及請求端點、參數信息,然后初始化了響應實例,接下來是調用 Client 的 Call 函數,所有請求調用處理的核心邏輯(服務發現、節點選擇、請求處理、超時、重試、編碼)都在這個函數里,最終源碼對應 ~/go/hello/src/github.com/micro/go-micro/client/rpc_client.go 的 Call 函數,這個函數代碼量較大,我們選取一些關鍵片段進行解讀。
首先會通過 rpcClient 類的 next 方法獲取遠程服務節點,在未設置系統環境變量 MICRO_PROXY_ADDRESS 的情況下會執行 Selector 的 Select 方法獲取服務節點,默認的 Selector 初始化操作位于 ~/go/hello/src/github.com/micro/go-micro/client/options.go 的 newOptions 方法(該方法會在 client.go 的初始化操作中調用):
if opts.Selector == nil {
opts.Selector = selector.NewSelector(
selector.Registry(opts.Registry),
)
}
這里我們可以看到 Selector 依賴于 Registry 組件,如果沒有額外設置的話,基于系統默認的 Registry 實現(這里是 Consul),NewSelector 方法源碼如下:
func NewSelector(opts ...Option) Selector {
sopts := Options{
Strategy: Random,
}
for _, opt := range opts {
opt(&sopts)
}
if sopts.Registry == nil {
sopts.Registry = registry.DefaultRegistry
}
s := ®istrySelector{
so: sopts,
}
s.rc = s.newCache()
return s
}
Selector 默認的負載均衡策略使用的是隨機算法(關于 Selector 支持的所有負載均衡算法后面我們還會單獨介紹),并且會在本地對節點選擇結果進行緩存。
回到 Selector 的 Select 函數,該函數源碼定義在 ~/go/hello/src/github.com/micro/go-micro/selector/default.go 中:
func (c *registrySelector) Select(service string, opts ...SelectOption) (Next, error) {
sopts := SelectOptions{
Strategy: c.so.Strategy,
}
for _, opt := range opts {
opt(&sopts)
}
// get the service
// try the cache first
// if that fails go directly to the registry
services, err := c.rc.GetService(service)
if err != nil {
return nil, err
}
// apply the filters
for _, filter := range sopts.Filters {
services = filter(services)
}
// if there's nothing left, return
if len(services) == 0 {
return nil, ErrNoneAvailable
}
return sopts.Strategy(services), nil
}
通過 c.rc.GetService(service) 傳入指定服務名稱,再通過默認 Registry 實現 Consul 獲取對應的服務實例列表并緩存(如果已緩存則直接返回提高性能,在通過 Registry 獲取服務節點列表時還會單獨跑一個協程去監聽服務注冊,如果有新節點注冊進來,則加到緩存中,如果有節點故障則刪除緩存中的節點信息,具體源碼位于 ~/go/hello/src/github.com/micro/go-micro/registry/cache/rcache.go),應用過濾器后最后通過默認負載均衡實現(這里是 Random 算法)返回指定節點,獲取到遠程服務實例節點后,就可以發起遠程服務請求了,回到 rpc_client.go 的 Call 函數,對應的遠程調用代碼邏輯實現片段如下:
call := func(i int) error {
...
// select next node
node, err := next()
if err != nil && err == selector.ErrNotFound {
return errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error())
} else if err != nil {
return errors.InternalServerError("go.micro.client", "error getting next %s node: %v", request.Service(), err.Error())
}
// make the call
err = rcall(ctx, node, request, response, callOpts)
r.opts.Selector.Mark(request.Service(), node, err)
return err
...
}
上述調用 rpcClient 的 next 函數返回的并不是真正的節點而是一個匿名函數,到 node, err := next() 這里才真正調用對應的函數返回遠程服務節點信息,如果返回節點成功則調用 rcall 函數(即 rpcClient 的 call 函數)發起遠程網絡請求(通過協程實現),如果請求處理出錯則返回相應錯誤信息,然后對服務調用成功與否通過 Selector 的 Mark 函數進行標記(以便后續對服務進行監控和治理),最后在 Call 函數中,也是通過協程發起對上述 call 匿名函數的調用:
...
for i := 0; i <= callOpts.Retries; i++ {
go func(i int) {
ch
}(i)
select {
case
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case err :=
// if the call succeeded lets bail early
if err == nil {
return nil
}
retry, rerr := callOpts.Retry(ctx, request, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
...
如果服務調用失敗,則進行重試或報錯處理。
以上就是在 Go Micro 體系內客戶端請求服務發現與節點選擇的底層實現,如果是以 HTTP 方式從外部通過 Micro API 網關形式對遠程服務發起請求,則 API 網關會將 HTTP 請求解析并轉化為默認的服務形式,比如 /greeter/say/hello 請求會被轉化為服務名為 go.micro.api.greeter,方法名為 Say.Hello 的請求,然后調用 go.micro.srv.greeter 遠程服務,后續處理邏輯與上面完全一致。請求處理完成后,返回處理結果給 API 網關,API 網關將其轉化為 HTTP 響應返回給調用客戶端。
總結
以上是生活随笔為你收集整理的f12控制台如何查看consul_基于 Consul 的 Go Micro 客户端服务发现是如何实现的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FLASH怎么制作LED灯?FLASH制
- 下一篇: $emit传递多个参数_10年架构师深解