亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang實(shí)現(xiàn)gRPC的Proxy的原理解析

 更新時(shí)間:2021年09月30日 08:37:01   作者:Mr.YF  
gRPC是Google開始的一個(gè)RPC服務(wù)框架, 是英文全名為Google Remote Procedure Call的簡稱,廣泛的應(yīng)用在有RPC場景的業(yè)務(wù)系統(tǒng)中,這篇文章主要介紹了Golang實(shí)現(xiàn)gRPC的Proxy的原理,需要的朋友可以參考下

背景

gRPC是Google開始的一個(gè)RPC服務(wù)框架, 是英文全名為Google Remote Procedure Call的簡稱。

廣泛的應(yīng)用在有RPC場景的業(yè)務(wù)系統(tǒng)中,一些架構(gòu)中將gRPC請求都經(jīng)過一個(gè)gRPC服務(wù)代理節(jié)點(diǎn)或網(wǎng)關(guān),進(jìn)行服務(wù)的權(quán)限現(xiàn)在,限流,服務(wù)調(diào)用簡化,增加請求統(tǒng)計(jì)等等諸多功能。

如下以Golang和gRPC為例,解析gRPC的轉(zhuǎn)發(fā)原理。

gRPC Proxy原理

基本原理如下

  • 基于TCP啟動(dòng)一個(gè)gRPC代理服務(wù)
  • 攔截gRPC框架的服務(wù)映射,能將gRPC請求的服務(wù)攔截到轉(zhuǎn)發(fā)代理的一個(gè)函數(shù)實(shí)現(xiàn)中。
  • 接收客戶端的請求,處理業(yè)務(wù)指標(biāo)后轉(zhuǎn)發(fā)給服務(wù)端。
  • 接收服務(wù)端的響應(yīng),處理業(yè)務(wù)指標(biāo)后轉(zhuǎn)發(fā)給客戶端。

基于如上原理描述,通過如下圖所示,gRPC的客戶端將所有的請求都發(fā)給gRPC Server Proxy,這個(gè)代理網(wǎng)關(guān)實(shí)現(xiàn)請求轉(zhuǎn)發(fā)。

將gRPC Client的請求流轉(zhuǎn)發(fā)到gRPC 服務(wù)實(shí)現(xiàn)的節(jié)點(diǎn)上。并將服務(wù)處理結(jié)果響應(yīng)返回給客戶端。

在這個(gè)圖中的轉(zhuǎn)發(fā)需要回答如下幾個(gè)問題

  • Proxy怎么知道哪些請求轉(zhuǎn)發(fā)到哪些服務(wù)節(jié)點(diǎn)上,轉(zhuǎn)發(fā)的依據(jù)是什么?
  • Proxy是否需要解析gRPC協(xié)議?
  • Proxy上沒有服務(wù)的實(shí)現(xiàn),該如何轉(zhuǎn)發(fā)?

簡化的gRPC服務(wù)處理流程

在回答如下問題之前,我們先簡單的分析一下gRPC服務(wù)器的實(shí)現(xiàn)原理和流程。

  • 編寫自己的服務(wù)實(shí)現(xiàn),例子中以HelloWorld為例。
  • 把自己的服務(wù)實(shí)現(xiàn)注冊到gRPC框架中
  • 創(chuàng)建一個(gè)TCP的服務(wù)端監(jiān)聽
  • 基于TCP監(jiān)聽啟動(dòng)一個(gè)gRPC服務(wù)
  • gRPC服務(wù)接收gRPC客戶端的TCP請求
  • 解析gRPC的頭部信息,找出服務(wù)名
  • 根據(jù)服務(wù)名找到第一步注冊的服務(wù)實(shí)現(xiàn)處理器
  • 處理函數(shù)執(zhí)行
  • 返回處理結(jié)果

簡化的注冊服務(wù)處理器函數(shù),啟動(dòng)gRPC服務(wù),調(diào)用請求和執(zhí)行數(shù)據(jù)流圖如下所示:

詳細(xì)的gRPC服務(wù)運(yùn)行原理

第一步,定義和編寫HelloWorld的IDL文件

syntax = "proto3";

package demoapi;


// HelloWorld Service
service HelloWorldService {
   rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse){};
}

// Request message
message HelloWorldRequest {
   string  request = 1;
}

// Response message
message HelloWorldResponse {
   string respose = 1;
}

在這個(gè)簡單的IDL中,定義了一個(gè)HelloWorldService的gRPC服務(wù),這個(gè)服務(wù)中有一個(gè)HelloWorld方法。

第二步,編譯IDL文件

將IDL的proto文件編譯成helloworld.pb.go的gRPC代碼文件。

生成的代碼文件中,我們可以看到如下信息

// Hello World的客戶端接口
type HelloWorldServiceClient interface {
    HelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error)
}

// Hello World的服務(wù)端接口
type HelloWorldServiceServer interface {
    HelloWorld(context.Context, *HelloWorldRequest) (*HelloWorldResponse, error)
}

// HelloWorld的服務(wù)注冊處理器函數(shù)Handler
func _HelloWorldService_HelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(HelloWorldRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(HelloWorldServiceServer).HelloWorld(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/demoapi.HelloWorldService/HelloWorld",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(HelloWorldServiceServer).HelloWorld(ctx, req.(*HelloWorldRequest))
    }
    return interceptor(ctx, in, info, handler)
}

// gRPC服務(wù)注冊的服務(wù)描述信息
// gRPC服務(wù)注冊時(shí),會建立以ServiceName為Key,Methods為Value的一個(gè)Map映射
// Methods中的Handler就是如上的服務(wù)處理Handler
var _HelloWorldService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "demoapi.HelloWorldService",
    HandlerType: (*HelloWorldServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "HelloWorld",
            Handler:    _HelloWorldService_HelloWorld_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "demoapi/HelloWorld.proto",
}

如上代碼中有如下幾個(gè)關(guān)鍵信息需要解釋

  • 服務(wù)Service名稱 demoapi.HelloWorldService,對應(yīng)IDL文件的package包名.service服務(wù)名稱
  • 方法Method名稱 HelloWorld,對應(yīng)IDL文件的rpc方法

第三步,注冊HelloWorld服務(wù)到gRPC的服務(wù)映射中

  • grpc.ServiceDesc是 gRPC服務(wù)注冊的服務(wù)描述信息。
  • gRPC服務(wù)注冊時(shí),會建立以ServiceName為Key,包裝Methods為Value的一個(gè)Map映射m。
  • Methods中的Handler就是如上的服務(wù)處理Handler。

對應(yīng)的注冊代碼如下

// 注冊gRPC服務(wù)
func RegisterHelloWorldServiceServer(s *grpc.Server, srv HelloWorldServiceServer) {
    s.RegisterService(&_HelloWorldService_serviceDesc, srv)
}

// Server is a gRPC server to serve RPC requests.
type Server struct {
       // ...
    m      map[string]*service // service name -> service info
}

// gRPC service.go的服務(wù)注冊
func (s *Server) register(sd *ServiceDesc, ss interface{}) {
    srv := &service{
        server: ss,
        md:     make(map[string]*MethodDesc),
        sd:     make(map[string]*StreamDesc),
        mdata:  sd.Metadata,
    }
    for i := range sd.Methods {
        d := &sd.Methods[i]
        srv.md[d.MethodName] = d
    }
    for i := range sd.Streams {
        d := &sd.Streams[i]
        srv.sd[d.StreamName] = d
    }
    s.m[sd.ServiceName] = srv
}

第四步,接收客戶端gRPC請求并處理

在這一步中,會進(jìn)行如下幾個(gè)步驟和函數(shù)的調(diào)用,也會回答前面的第一個(gè)問題。

  • gRPC客戶端通過TCP鏈接,連接到gRPC服務(wù)端
  • gRPC的Serve函數(shù)觸發(fā)TCP的Accept函數(shù)調(diào)用,生成一個(gè)和客戶端的網(wǎng)絡(luò)連接
  • grpc框架代碼執(zhí)行handleRawConn方法,將這個(gè)網(wǎng)絡(luò)連接設(shè)置打破gRPC的傳輸層,做為網(wǎng)絡(luò)的讀和寫實(shí)現(xiàn)
  • 依次調(diào)用grpc流的handlerStream方法,用于處理gRPC數(shù)據(jù)流
  • 這個(gè)函數(shù)中會接收gRPC請求的頭信息,并解析得到服務(wù)名 如第二步中的服務(wù)名 demoapi.HelloWorldService
  • 通過如下的服務(wù)名中的方法名HelloWorld,并在Method的map中找到這個(gè)方法的處理器函數(shù)Handler,并執(zhí)行這個(gè)Handler函數(shù),實(shí)現(xiàn)gRPC服務(wù)的調(diào)用
  • 最后將處理結(jié)果返回

整體的數(shù)據(jù)流整理如下:

我們發(fā)現(xiàn)在gRPC框架代碼中的handleStream存在兩類服務(wù),一類是已知服務(wù) knownService, 第二類是unknownService

這兩個(gè)有什么區(qū)別呢?

已知服務(wù) knownService就是gRPC服務(wù)端代碼注冊到gRPC框架中的服務(wù),叫做已知服務(wù),其他沒有注冊的服務(wù)叫做未知服務(wù)。

為什么我們要提到這個(gè)未知服務(wù)unknownService呢?著就是我們實(shí)現(xiàn)gRPC服務(wù)代碼的關(guān)鍵所在,是前面問題三的答案,

要實(shí)現(xiàn)gRPC服務(wù)代理,我們在創(chuàng)建grpc服務(wù)grpc.NewServer時(shí),傳遞一個(gè)未知服務(wù)的handler,將未知服務(wù)的處理進(jìn)行接管,然后通過注冊的這個(gè)Handler實(shí)現(xiàn)gRPC代理轉(zhuǎn)發(fā)的邏輯。

基于如下描述,gRPC代理的原理如下圖所示:

  • 創(chuàng)建grpc服務(wù)時(shí),注冊一個(gè)未知服務(wù)處理器Handler和一個(gè)自定義的編碼Codec編碼和解碼,此處使用proto標(biāo)準(zhǔn)的Codec(回答簽名第二個(gè)問題)
  • 這個(gè)handle給業(yè)務(wù)方預(yù)留一個(gè)director的接口,用于代理重定向轉(zhuǎn)發(fā)的grpc連接獲取,這樣proxy就可以通過redirector得到gRPCServer的grpc連接。
  • proxy接收gRPC客戶端的連接,并使用gRPC的RecvMsg方法,接收客戶端的消息請求
  • proxy將接收到的gRPC客戶端消息請求,通過SendHeader和SendMsg方法發(fā)送給gRPC服務(wù)端。
  • 同樣的方法,RecvMsg接收gRPC服務(wù)端的響應(yīng)消息,使用SendMsg發(fā)送給gRPC客戶端。
  • 至此gRPC代碼服務(wù)就完成了消息的轉(zhuǎn)發(fā)功能,企業(yè)的限流,權(quán)限等功能可以通過轉(zhuǎn)發(fā)的功能進(jìn)行攔截處理。

gRPC Proxy的實(shí)現(xiàn)邏輯如下圖所示:

gRPC 代理服務(wù)的關(guān)鍵代碼如下所示:

服務(wù)端到客戶端的轉(zhuǎn)發(fā)

// 轉(zhuǎn)發(fā)服務(wù)端的數(shù)據(jù)流到客戶端
func (s *handler) forwardServerToClient(src grpc.ServerStream, dst grpc.ClientStream) chan error {
    ret := make(chan error, 1)
    go func() {
        f := &frame{}
        for i := 0; ; i++ {
            if err := src.RecvMsg(f); err != nil {
                ret <- err // this can be io.EOF which is happy case
                break
            }
            if err := dst.SendMsg(f); err != nil {
                ret <- err
                break
            }
        }
    }()
    return ret
}

客戶端到服務(wù)端的轉(zhuǎn)發(fā)

// 轉(zhuǎn)發(fā)客戶端的數(shù)據(jù)流到服務(wù)端
func (s *handler) forwardClientToServer(src grpc.ClientStream, dst grpc.ServerStream) chan error {
    ret := make(chan error, 1)
    go func() {
        f := &frame{}
        for i := 0; ; i++ {
            if err := src.RecvMsg(f); err != nil {
                ret <- err // this can be io.EOF which is happy case
                break
            }
            if i == 0 {
                // This is a bit of a hack, but client to server headers are only readable after first client msg is
                // received but must be written to server stream before the first msg is flushed.
                // This is the only place to do it nicely.
                md, err := src.Header()
                if err != nil {
                    ret <- err
                    break
                }
                if err := dst.SendHeader(md); err != nil {
                    ret <- err
                    break
                }
            }
            if err := dst.SendMsg(f); err != nil {
                ret <- err
                break
            }
        }
    }()
    return ret
}

參考材料

https://github.com/grpc/grpc

https://github.com/mwitkow/grpc-proxy

到此這篇關(guān)于Golang實(shí)現(xiàn)gRPC的Proxy的原理的文章就介紹到這了,更多相關(guān)Golang gRPC的Proxy的原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go語言使用scp的方法實(shí)例分析

    go語言使用scp的方法實(shí)例分析

    這篇文章主要介紹了go語言使用scp的方法,實(shí)例分析了go語言調(diào)用scp命令的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • Golang發(fā)送http GET請求的示例代碼

    Golang發(fā)送http GET請求的示例代碼

    這篇文章主要介紹了Golang發(fā)送http GET請求的示例代碼,幫助大家更好的理解和使用golang,感興趣的朋友可以了解下
    2020-12-12
  • Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn)

    Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn)

    本文主要介紹了Golang 字符串轉(zhuǎn)time類型實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • golang?beego框架環(huán)境搭建過程

    golang?beego框架環(huán)境搭建過程

    這篇文章主要為大家介紹了golang?beego框架環(huán)境搭建的過程腳本,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Golang 類型轉(zhuǎn)換的實(shí)現(xiàn)(斷言、強(qiáng)制、顯式類型)

    Golang 類型轉(zhuǎn)換的實(shí)現(xiàn)(斷言、強(qiáng)制、顯式類型)

    將一個(gè)值從一種類型轉(zhuǎn)換到另一種類型,便發(fā)生了類型轉(zhuǎn)換,在go可以分為斷言、強(qiáng)制、顯式類型轉(zhuǎn)換,本文就詳細(xì)的介紹一下這就幾種轉(zhuǎn)換方式,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解

    go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解

    這篇文章主要為大家介紹了go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang實(shí)現(xiàn)http重定向https

    Golang實(shí)現(xiàn)http重定向https

    這篇文章介紹了Golang實(shí)現(xiàn)http重定向https的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Go語言net包RPC遠(yuǎn)程調(diào)用三種方式http與json-rpc及tcp

    Go語言net包RPC遠(yuǎn)程調(diào)用三種方式http與json-rpc及tcp

    這篇文章主要為大家介紹了Go語言net包RPC遠(yuǎn)程調(diào)用三種方式分別使用http與json-rpc及tcp的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • 使用go語言實(shí)現(xiàn)cors中間件

    使用go語言實(shí)現(xiàn)cors中間件

    CORS是一種瀏覽器安全機(jī)制,用于控制在Web應(yīng)用程序中不同源(Origin)之間的資源共享,本文將給大家介紹如何使用go語言實(shí)現(xiàn)cors中間件,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下
    2023-09-09
  • Golang連接PostgreSQL基本操作的實(shí)現(xiàn)

    Golang連接PostgreSQL基本操作的實(shí)現(xiàn)

    PostgreSQL是常見的免費(fèi)的大型關(guān)系型數(shù)據(jù)庫,本文主要介紹了Golang連接PostgreSQL基本操作的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02

最新評論