go基於grpc構建微服務架構-服務註冊與發現

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

grpc 是Google開源的rpc架構,基於http2實現,並支援跨語言,目前基本涵蓋了主流語言.跨語言的實現主要得益於protobuf,通過編寫proto檔案,通過protobuf工具產生對應語言的類庫進行使用.

對於go這樣一門新生語言來說,生態鏈還處於發展階段,微服務架構也是如此,下面將基於grpc-go版本搭建一個微服務通訊架構.

1.服務註冊與發布的機制

1.1 解決的問題

服務註冊與發布主要解決的服務依賴問題,通常意義上,如果A服務調用B服務時,最直接的做法是配置IP地址和連接埠.但隨著服務依賴變多時,配置將會是否龐雜,且當服務發生遷移時,那麼所有相關服務的配置均需要修改,這將十分難以維護以及容易出現問題.
因此為瞭解決這種服務依賴關係,服務註冊與發布應運而生.

1.2 機制

服務註冊與發現主要分為以下幾點.

  • 服務資訊發布

這裡主要是服務的服務名,IP資訊,以及一些附件中繼資料.通過註冊介面註冊到服務註冊發布中心.

  • 存活檢測

當服務意外停止時,用戶端需要感知到服務停止,並將服務的IP地址踢出可用的IP地址清單,這裡可以使用定時心跳去實現.

  • 用戶端負載平衡

通過服務註冊與發布,可以實現一個服務部署多台執行個體,用戶端實現在執行個體直接的負載平衡,從而實現服務的橫向擴充.

因此,服務註冊與發布可以概況為,服務將資訊上報,用戶端拉取服務資訊,通過服務名進行調用,當服務宕機時用戶端踢掉故障服務,服務新上線時用戶端自動添加到調用列表.

2.實現

grpc-go的整個實現大量使用go的介面特性,因此通過擴充介面,可以很容易的實現服務的註冊與發現,這裡服務註冊中心考慮到可用性以及一致性,一般採用etcd或zookeeper來實現,這裡實現etcd的版本.
完整代碼以及使用樣本見:https://github.com/g4zhuj/grp...

2.1 用戶端

具體需要實現幾個介面,針對用戶端,最簡單的實現方式只需要實現兩個介面方法Resolve(),以及Next(),然後使用輪詢的負載平衡方式.
主要通過etcd的Get介面以及Watch介面實現.

  • Resolve()介面
//用於產生Watcher,監聽註冊中心中的服務資訊變化func (er *etcdRegistry) Resolve(target string) (naming.Watcher, error) {    ctx, cancel := context.WithTimeout(context.TODO(), resolverTimeOut)    w := &etcdWatcher{        cli:    er.cli,        target: target + "/",        ctx:    ctx,        cancel: cancel,    }    return w, nil}
  • Next() 介面
//Next介面主要用於擷取註冊的服務資訊,通過channel以及watch,當服務資訊發生//變化時,Next介面會將變化返回給grpc架構從而實現服務資訊變更.func (ew *etcdWatcher) Next() ([]*naming.Update, error) {    var updates []*naming.Update    //初次擷取時,建立監聽channel,並返回擷取到的服務資訊    if ew.watchChan == nil {        //create new chan        resp, err := ew.cli.Get(ew.ctx, ew.target, etcd.WithPrefix(), etcd.WithSerializable())        if err != nil {            return nil, err        }        for _, kv := range resp.Kvs {            var upt naming.Update            if err := json.Unmarshal(kv.Value, &upt); err != nil {                continue            }            updates = append(updates, &upt)        }        //建立etcd的watcher監聽target(服務名)的資訊.        opts := []etcd.OpOption{etcd.WithRev(resp.Header.Revision + 1), etcd.WithPrefix(), etcd.WithPrevKV()}        ew.watchChan = ew.cli.Watch(context.TODO(), ew.target, opts...)        return updates, nil    }    //阻塞監聽,服務發生變化時才返回給上層    wrsp, ok := <-ew.watchChan    if !ok {        err := status.Error(codes.Unavailable, "etcd watch closed")        return nil, err    }    if wrsp.Err() != nil {        return nil, wrsp.Err()    }    for _, e := range wrsp.Events {        var upt naming.Update        var err error        switch e.Type {        case etcd.EventTypePut:            err = json.Unmarshal(e.Kv.Value, &upt)            upt.Op = naming.Add        case etcd.EventTypeDelete:            err = json.Unmarshal(e.PrevKv.Value, &upt)            upt.Op = naming.Delete        }        if err != nil {            continue        }        updates = append(updates, &upt)    }    return updates, nil}

2.2 服務端

服務端只需要上報服務資訊,並定時保持心跳,這裡通過etcd的Put介面以及KeepAlive介面實現.
具體如下:

func (er *etcdRegistry) Register(ctx context.Context, target string, update naming.Update, opts ...wrapper.RegistryOptions) (err error) {    //將服務資訊序列化成json格式    var upBytes []byte    if upBytes, err = json.Marshal(update); err != nil {        return status.Error(codes.InvalidArgument, err.Error())    }    ctx, cancel := context.WithTimeout(context.TODO(), resolverTimeOut)    er.cancal = cancel    rgOpt := wrapper.RegistryOption{TTL: wrapper.DefaultRegInfTTL}    for _, opt := range opts {        opt(&rgOpt)    }    switch update.Op {    case naming.Add:        lsRsp, err := er.lsCli.Grant(ctx, int64(rgOpt.TTL/time.Second))        if err != nil {            return err        }        //Put服務資訊到etcd,並設定key的值TTL,通過後面的KeepAlive介面        //對TTL進行續期,超過TTL的時間未收到續期請求,則說明服務可能掛了,從而清除服務資訊        etcdOpts := []etcd.OpOption{etcd.WithLease(lsRsp.ID)}        key := target + "/" + update.Addr        _, err = er.cli.KV.Put(ctx, key, string(upBytes), etcdOpts...)        if err != nil {            return err        }        //保持心跳        lsRspChan, err := er.lsCli.KeepAlive(context.TODO(), lsRsp.ID)        if err != nil {            return err        }        go func() {            for {                _, ok := <-lsRspChan                if !ok {                    grpclog.Fatalf("%v keepalive channel is closing", key)                    break                }            }        }()    case naming.Delete:        _, err = er.cli.Delete(ctx, target+"/"+update.Addr)    default:        return status.Error(codes.InvalidArgument, "unsupported op")    }    return nil}

3. 參考

https://grpc.io/
https://coreos.com/etcd/
https://github.com/g4zhuj/grp...

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.