您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)基于gRPC的注冊發(fā)現(xiàn)與負載均衡的原理和實戰(zhàn)是怎么樣的,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
gRPC是一個現(xiàn)代的、高性能、開源的和語言無關(guān)的通用RPC框架,基于HTTP2協(xié)議設(shè)計,序列化使用PB(Protocol Buffer),PB是一種語言無關(guān)的高性能序列化框架,基于HTTP2+PB保證了的高性能。go-zero是一個開源的微服務(wù)框架,支持http和rpc協(xié)議,其中rpc底層依賴gRPC,本文會結(jié)合gRPC和go-zero源碼從實戰(zhàn)的角度和大家一起分析下服務(wù)注冊與發(fā)現(xiàn)和負載均衡的實現(xiàn)原理
原理流程圖如下:
從圖中可以看出go-zero實現(xiàn)了gRPC的resolver和balancer接口,然后通過gprc.Register方法注冊到gRPC中,resolver模塊提供了服務(wù)注冊的功能,balancer模塊提供了負載均衡的功能。當(dāng)client發(fā)起服務(wù)調(diào)用的時候會根據(jù)resolver注冊進來的服務(wù)列表,使用注冊進來的balancer選擇一個服務(wù)發(fā)起請求,如果沒有進行注冊gRPC會使用默認的resolver和balancer。服務(wù)地址的變更會同步到etcd中,go-zero監(jiān)聽etcd的變化通過resolver更新服務(wù)列表
通過resolver.Register方法可以注冊自定義的Resolver,Register方法定義如下,其中Builder為interface類型,因此自定義resolver需要實現(xiàn)該接口,Builder定義如下
// Register 注冊自定義resolver func Register(b Builder) { m[b.Scheme()] = b } // Builder 定義resolver builder type Builder interface { Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error) Scheme() string }
Build方法的第一個參數(shù)target的類型為Target定義如下,創(chuàng)建ClientConn調(diào)用grpc.DialContext的第二個參數(shù)target經(jīng)過解析后需要符合這個結(jié)構(gòu)定義,target定義格式為: scheme://authority/endpoint_name
type Target struct { Scheme string // 表示要使用的名稱系統(tǒng) Authority string // 表示一些特定于方案的引導(dǎo)信息 Endpoint string // 指出一個具體的名字 }
Build方法返回的Resolver也是一個接口類型。定義如下
type Resolver interface { ResolveNow(ResolveNowOptions) Close() }
流程圖下圖
因此可以看出自定義Resolver需要實現(xiàn)如下步驟:
定義target
實現(xiàn)resolver.Builder
實現(xiàn)resolver.Resolver
調(diào)用resolver.Register注冊自定義的Resolver,其中name為target中的scheme
實現(xiàn)服務(wù)發(fā)現(xiàn)邏輯(etcd、consul、zookeeper)
通過resolver.ClientConn實現(xiàn)服務(wù)地址的更新
go-zero中target的定義如下,默認的名字為discov
// BuildDiscovTarget 構(gòu)建target func BuildDiscovTarget(endpoints []string, key string) string { return fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme, strings.Join(endpoints, resolver.EndpointSep), key) } // RegisterResolver 注冊自定義的Resolver func RegisterResolver() { resolver.Register(&dirBuilder) resolver.Register(&disBuilder) }
Build方法的實現(xiàn)如下
func (d *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) ( resolver.Resolver, error) { hosts := strings.FieldsFunc(target.Authority, func(r rune) bool { return r == EndpointSepChar }) // 獲取服務(wù)列表 sub, err := discov.NewSubscriber(hosts, target.Endpoint) if err != nil { return nil, err } update := func() { var addrs []resolver.Address for _, val := range subset(sub.Values(), subsetSize) { addrs = append(addrs, resolver.Address{ Addr: val, }) } // 調(diào)用UpdateState方法更新 cc.UpdateState(resolver.State{ Addresses: addrs, }) } // 添加監(jiān)聽,當(dāng)服務(wù)地址發(fā)生變化會觸發(fā)更新 sub.AddListener(update) // 更新服務(wù)列表 update() return &nopResolver{cc: cc}, nil }
那么注冊進來的resolver在哪里用到的呢?當(dāng)創(chuàng)建客戶端的時候調(diào)用DialContext方法創(chuàng)建ClientConn的時候回進行如下操作
攔截器處理
各種配置項處理
解析target
獲取resolver
創(chuàng)建ccResolverWrapper
創(chuàng)建clientConn的時候回根據(jù)target解析出scheme,然后根據(jù)scheme去找已注冊對應(yīng)的resolver,如果沒有找到則使用默認的resolver
ccResolverWrapper的流程如下圖,在這里resolver會和balancer會進行關(guān)聯(lián),balancer的處理方式和resolver類似也是通過wrapper進行了一次封裝
緊著著會根據(jù)獲取到的地址創(chuàng)建htt2的鏈接
到此ClientConn創(chuàng)建過程基本結(jié)束,我們再一起梳理一下整個過程,首先獲取resolver,其中ccResolverWrapper實現(xiàn)了resovler.ClientConn接口,通過Resolver的UpdateState方法觸發(fā)獲取Balancer,獲取Balancer,其中ccBalancerWrapper實現(xiàn)了balancer.ClientConn接口,通過Balnacer的UpdateClientConnState方法觸發(fā)創(chuàng)建連接(SubConn),最后創(chuàng)建HTTP2 Client
balancer模塊用來在客戶端發(fā)起請求時進行負載均衡,如果沒有注冊自定義的balancer的話gRPC會采用默認的負載均衡算法,流程圖如下
在go-zero中自定義的balancer主要實現(xiàn)了如下步驟:
實現(xiàn)PickerBuilder,Build方法返回balancer.Picker
實現(xiàn)balancer.Picker,Pick方法實現(xiàn)負載均衡算法邏輯
調(diào)用balancer.Registet注冊自定義Balancer
使用baseBuilder注冊,框架已提供了baseBuilder和baseBalancer實現(xiàn)了Builer和Balancer
Build方法的實現(xiàn)如下
func (b *p2cPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker { if len(readySCs) == 0 { return base.NewErrPicker(balancer.ErrNoSubConnAvailable) } var conns []*subConn for addr, conn := range readySCs { conns = append(conns, &subConn{ addr: addr, conn: conn, success: initSuccess, }) } return &p2cPicker{ conns: conns, r: rand.New(rand.NewSource(time.Now().UnixNano())), stamp: syncx.NewAtomicDuration(), } }
go-zero中默認實現(xiàn)了p2c負載均衡算法,該算法的優(yōu)勢是能彈性的處理各個節(jié)點的請求,Pick的實現(xiàn)如下
func (p *p2cPicker) Pick(ctx context.Context, info balancer.PickInfo) ( conn balancer.SubConn, done func(balancer.DoneInfo), err error) { p.lock.Lock() defer p.lock.Unlock() var chosen *subConn switch len(p.conns) { case 0: return nil, nil, balancer.ErrNoSubConnAvailable // 沒有可用鏈接 case 1: chosen = p.choose(p.conns[0], nil) // 只有一個鏈接 case 2: chosen = p.choose(p.conns[0], p.conns[1]) default: // 選擇一個健康的節(jié)點 var node1, node2 *subConn for i := 0; i < pickTimes; i++ { a := p.r.Intn(len(p.conns)) b := p.r.Intn(len(p.conns) - 1) if b >= a { b++ } node1 = p.conns[a] node2 = p.conns[b] if node1.healthy() && node2.healthy() { break } } chosen = p.choose(node1, node2) } atomic.AddInt64(&chosen.inflight, 1) atomic.AddInt64(&chosen.requests, 1) return chosen.conn, p.buildDoneFunc(chosen), nil }
客戶端發(fā)起調(diào)用的流程如下,會調(diào)用pick方法獲取一個transport進行處理
以上就是基于gRPC的注冊發(fā)現(xiàn)與負載均衡的原理和實戰(zhàn)是怎么樣的,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。