中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

發布時間:2021-12-03 15:06:55 來源:億速云 閱讀:246 作者:柒染 欄目:云計算

本篇文章給大家分享的是有關基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

gRPC是一個現代的、高性能、開源的和語言無關的通用RPC框架,基于HTTP2協議設計,序列化使用PB(Protocol Buffer),PB是一種語言無關的高性能序列化框架,基于HTTP2+PB保證了的高性能。go-zero是一個開源的微服務框架,支持http和rpc協議,其中rpc底層依賴gRPC,本文會結合gRPC和go-zero源碼從實戰的角度和大家一起分析下服務注冊與發現和負載均衡的實現原理

基本原理

原理流程圖如下:

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

從圖中可以看出go-zero實現了gRPC的resolver和balancer接口,然后通過gprc.Register方法注冊到gRPC中,resolver模塊提供了服務注冊的功能,balancer模塊提供了負載均衡的功能。當client發起服務調用的時候會根據resolver注冊進來的服務列表,使用注冊進來的balancer選擇一個服務發起請求,如果沒有進行注冊gRPC會使用默認的resolver和balancer。服務地址的變更會同步到etcd中,go-zero監聽etcd的變化通過resolver更新服務列表

Resolver模塊

通過resolver.Register方法可以注冊自定義的Resolver,Register方法定義如下,其中Builder為interface類型,因此自定義resolver需要實現該接口,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方法的第一個參數target的類型為Target定義如下,創建ClientConn調用grpc.DialContext的第二個參數target經過解析后需要符合這個結構定義,target定義格式為: scheme://authority/endpoint_name

type Target struct {
	Scheme    string // 表示要使用的名稱系統
	Authority string // 表示一些特定于方案的引導信息
	Endpoint  string // 指出一個具體的名字
}

Build方法返回的Resolver也是一個接口類型。定義如下

type Resolver interface {
	ResolveNow(ResolveNowOptions)
	Close()
}

流程圖下圖

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

因此可以看出自定義Resolver需要實現如下步驟:

  • 定義target

  • 實現resolver.Builder

  • 實現resolver.Resolver

  • 調用resolver.Register注冊自定義的Resolver,其中name為target中的scheme

  • 實現服務發現邏輯(etcd、consul、zookeeper)

  • 通過resolver.ClientConn實現服務地址的更新

go-zero中target的定義如下,默認的名字為discov

// BuildDiscovTarget 構建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方法的實現如下

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
	})
  // 獲取服務列表
	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,
			})
		}
    // 調用UpdateState方法更新
		cc.UpdateState(resolver.State{
			Addresses: addrs,
		})
	}
  
  // 添加監聽,當服務地址發生變化會觸發更新
	sub.AddListener(update)
  // 更新服務列表
	update()

	return &nopResolver{cc: cc}, nil
}

那么注冊進來的resolver在哪里用到的呢?當創建客戶端的時候調用DialContext方法創建ClientConn的時候回進行如下操作

  • 攔截器處理

  • 各種配置項處理

  • 解析target

  • 獲取resolver

  • 創建ccResolverWrapper

創建clientConn的時候回根據target解析出scheme,然后根據scheme去找已注冊對應的resolver,如果沒有找到則使用默認的resolver

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

ccResolverWrapper的流程如下圖,在這里resolver會和balancer會進行關聯,balancer的處理方式和resolver類似也是通過wrapper進行了一次封裝

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

緊著著會根據獲取到的地址創建htt2的鏈接

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

到此ClientConn創建過程基本結束,我們再一起梳理一下整個過程,首先獲取resolver,其中ccResolverWrapper實現了resovler.ClientConn接口,通過Resolver的UpdateState方法觸發獲取Balancer,獲取Balancer,其中ccBalancerWrapper實現了balancer.ClientConn接口,通過Balnacer的UpdateClientConnState方法觸發創建連接(SubConn),最后創建HTTP2 Client

Balancer模塊

balancer模塊用來在客戶端發起請求時進行負載均衡,如果沒有注冊自定義的balancer的話gRPC會采用默認的負載均衡算法,流程圖如下

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

在go-zero中自定義的balancer主要實現了如下步驟:

  • 實現PickerBuilder,Build方法返回balancer.Picker

  • 實現balancer.Picker,Pick方法實現負載均衡算法邏輯

  • 調用balancer.Registet注冊自定義Balancer

  • 使用baseBuilder注冊,框架已提供了baseBuilder和baseBalancer實現了Builer和Balancer

Build方法的實現如下

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中默認實現了p2c負載均衡算法,該算法的優勢是能彈性的處理各個節點的請求,Pick的實現如下

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: // 選擇一個健康的節點
		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
}

客戶端發起調用的流程如下,會調用pick方法獲取一個transport進行處理

基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的

以上就是基于gRPC的注冊發現與負載均衡的原理和實戰是怎么樣的,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

大荔县| 绥阳县| 砀山县| 六枝特区| 阿拉善右旗| 台东市| 咸宁市| 仪陇县| 灵台县| 绿春县| 油尖旺区| 镇雄县| 昌都县| 沾化县| 峨山| 新密市| 类乌齐县| 宁陕县| 铜川市| 茶陵县| 洞口县| 大洼县| 洪雅县| 资讯| 黎平县| 湘西| 陈巴尔虎旗| 金塔县| 波密县| 双辽市| 赫章县| 南溪县| 伽师县| 盘锦市| 汾西县| 金湖县| 内乡县| 峡江县| 海伦市| 镇沅| 丹江口市|