f12控制台如何查看consul_基于 Consul 的 Go Micro 客户端服务发现是如何实现的
基于 Consul 的 Go Micro 客戶端服務發(fā)現(xiàn)是如何實現(xiàn)的
由 學院君 創(chuàng)建于1年前, 最后更新于 1年前
版本號 #1
上篇分享我們介紹了基于 Consul 作為注冊中心的 Go Micro 服務注冊底層實現(xiàn)原理,今天我們來看看 Go Micro 中客戶端服務發(fā)現(xiàn)是如何實現(xiàn)的。
客戶端服務發(fā)現(xiàn)要復雜一些,涉及到服務發(fā)現(xiàn) Registry 和節(jié)點選擇 Selector 兩部分。
所謂服務發(fā)現(xiàn)指的是當我們從客戶端向指定服務發(fā)起請求時,可以通過名字識別服務,然后通過服務發(fā)現(xiàn)獲取到包含 IP 地址和端口號的對應遠程服務實例,遠程服務會在啟動時向注冊中心注冊,退出時注銷,客戶端無需關心這些細節(jié),由 Go Micro 的 Registry 組件統(tǒng)一處理服務注冊與發(fā)現(xiàn)邏輯(這里,我們基于 Consul 作為 Registry 的具體實現(xiàn)插件)。
而節(jié)點選擇指的是,遠程服務實例通常部署在多個節(jié)點上,通過指定的服務名稱可以獲取到一個地址列表,節(jié)點選擇要做的事情是通過某種策略從列表中獲取指定的 IP 進行訪問,這就是 Go Micro 中 Selector 組件發(fā)揮作用的地方,它基于 Registry 組件實現(xiàn),提供了負載均衡策略,比如輪詢或隨機,以及過濾、緩存和黑名單的功能。
下面我們還是通過分析客戶端調用底層源碼來看下 Go Micro 框架中服務發(fā)現(xiàn)與節(jié)點選擇的具體實現(xiàn)。首先打開 ~/go/hello/src/hello/client.go 文件,在 main 函數(shù)中,通過一系列的初始化操作后,真正發(fā)起服務調用的代碼是 greeter.Hello 函數(shù)調用:
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 函數(shù)定義在 ~/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
}
在這個函數(shù)中,通過 NewRequest 初始化了請求實例,包含服務名稱及請求端點、參數(shù)信息,然后初始化了響應實例,接下來是調用 Client 的 Call 函數(shù),所有請求調用處理的核心邏輯(服務發(fā)現(xiàn)、節(jié)點選擇、請求處理、超時、重試、編碼)都在這個函數(shù)里,最終源碼對應 ~/go/hello/src/github.com/micro/go-micro/client/rpc_client.go 的 Call 函數(shù),這個函數(shù)代碼量較大,我們選取一些關鍵片段進行解讀。
首先會通過 rpcClient 類的 next 方法獲取遠程服務節(jié)點,在未設置系統(tǒng)環(huán)境變量 MICRO_PROXY_ADDRESS 的情況下會執(zhí)行 Selector 的 Select 方法獲取服務節(jié)點,默認的 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 組件,如果沒有額外設置的話,基于系統(tǒng)默認的 Registry 實現(xiàn)(這里是 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 支持的所有負載均衡算法后面我們還會單獨介紹),并且會在本地對節(jié)點選擇結果進行緩存。
回到 Selector 的 Select 函數(shù),該函數(shù)源碼定義在 ~/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 實現(xiàn) Consul 獲取對應的服務實例列表并緩存(如果已緩存則直接返回提高性能,在通過 Registry 獲取服務節(jié)點列表時還會單獨跑一個協(xié)程去監(jiān)聽服務注冊,如果有新節(jié)點注冊進來,則加到緩存中,如果有節(jié)點故障則刪除緩存中的節(jié)點信息,具體源碼位于 ~/go/hello/src/github.com/micro/go-micro/registry/cache/rcache.go),應用過濾器后最后通過默認負載均衡實現(xiàn)(這里是 Random 算法)返回指定節(jié)點,獲取到遠程服務實例節(jié)點后,就可以發(fā)起遠程服務請求了,回到 rpc_client.go 的 Call 函數(shù),對應的遠程調用代碼邏輯實現(xiàn)片段如下:
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 函數(shù)返回的并不是真正的節(jié)點而是一個匿名函數(shù),到 node, err := next() 這里才真正調用對應的函數(shù)返回遠程服務節(jié)點信息,如果返回節(jié)點成功則調用 rcall 函數(shù)(即 rpcClient 的 call 函數(shù))發(fā)起遠程網(wǎng)絡請求(通過協(xié)程實現(xiàn)),如果請求處理出錯則返回相應錯誤信息,然后對服務調用成功與否通過 Selector 的 Mark 函數(shù)進行標記(以便后續(xù)對服務進行監(jiān)控和治理),最后在 Call 函數(shù)中,也是通過協(xié)程發(fā)起對上述 call 匿名函數(shù)的調用:
...
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 體系內客戶端請求服務發(fā)現(xiàn)與節(jié)點選擇的底層實現(xiàn),如果是以 HTTP 方式從外部通過 Micro API 網(wǎng)關形式對遠程服務發(fā)起請求,則 API 網(wǎng)關會將 HTTP 請求解析并轉化為默認的服務形式,比如 /greeter/say/hello 請求會被轉化為服務名為 go.micro.api.greeter,方法名為 Say.Hello 的請求,然后調用 go.micro.srv.greeter 遠程服務,后續(xù)處理邏輯與上面完全一致。請求處理完成后,返回處理結果給 API 網(wǎng)關,API 網(wǎng)關將其轉化為 HTTP 響應返回給調用客戶端。
總結
以上是生活随笔為你收集整理的f12控制台如何查看consul_基于 Consul 的 Go Micro 客户端服务发现是如何实现的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FLASH怎么制作LED灯?FLASH制
- 下一篇: $emit传递多个参数_10年架构师深解