grpc

Go的RPC標(biāo)準(zhǔn)庫

簡單使用

Go語言標(biāo)準(zhǔn)庫(net/rpc)的RPC規(guī)則:方法只能有兩個可序列化的參數(shù),其中第二個參數(shù)是指針類型,并且返回一個error類型,同時必須是公開的方法。

type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}
func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }
    conn, err := listener.Accept()
    if err != nil {
        log.Fatal("Accept error:", err)
    }
    rpc.ServeConn(conn)
}

一個服務(wù)可以有多個方法,rpc.RegisterName函數(shù)調(diào)用會將對象類型中所有滿足RPC規(guī)則的對象方法注冊為RPC函數(shù),所有注冊的方法會放在“HelloService”服務(wù)空間之下。

客戶端代碼:

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    var reply string
    err = client.Call("HelloService.Hello", "Tom", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}

跨語言使用

Go的rpc標(biāo)準(zhǔn)庫請求模板.PNG
Go標(biāo)準(zhǔn)庫的rpc響應(yīng)模板.PNG

{"method":"HelloService.Hello","params":["hello"],"id":1}
{"id":1,"result":"hello:Tom","error":null}
無論采用何種語言,只要遵循同樣的json結(jié)構(gòu),以同樣的流程就可以和Go語言編寫的RPC服務(wù)進行通信。這樣我們就實現(xiàn)了跨語言的RPC。

HTTP上的RPC

func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
        var conn io.ReadWriteCloser = struct {
            io.Writer
            io.ReadCloser
        }{
            ReadCloser: r.Body,
            Writer:     w,
        }
        rpc.ServeRequest(jsonrpc.NewServerCodec(conn)) 
   })
   http.ListenAndServe(":1234", nil)
}

Protobuf

Protobuf是Protocol Buffers的簡稱,它是Google公司開發(fā)的一種數(shù)據(jù)描述語言,并于2008年對外開源。Protobuf剛開源時的定位類似于XML、JSON等數(shù)據(jù)描述語言,通過附帶工具生成代碼并實現(xiàn)將結(jié)構(gòu)化數(shù)據(jù)序列化的功能。但是我們更關(guān)注的是Protobuf作為接口規(guī)范的描述語言,可以作為設(shè)計安全的跨語言PRC接口的基礎(chǔ)工具。

#hello.proto
syntax = "proto3";
option go_package = ".;main";
package main;
message String {
    string value = 1;
}

Protobuf核心的工具集是C++語言開發(fā)的,安裝官方的protoc工具,可以從https://github.com/google/protobuf/releases下載。在官方的protoc編譯器中并不支持Go語言。要想基于上面的hello.proto文件生成相應(yīng)的Go代碼,需要安裝相應(yīng)的插件。然后是安裝針對Go語言的代碼生成插件,可以通過go get google.golang.org/protobuf/cmd/protoc-gen-go命令安裝(這個是老版本的:github.com/golang/protobuf/protoc-gen-go)。然后通過以下命令生成相應(yīng)的Go代碼:protoc --go_out=. hello.proto其中g(shù)o_out參數(shù)告知protoc編譯器去加載對應(yīng)的protoc-gen-go工具,然后通過該工具生成代碼,生成代碼放到當(dāng)前目錄,最后是一系列要處理的protobuf文件的列表,此時目錄下會有一個hello.pb.go的文件。

不過用Protobuf定義語言無關(guān)的RPC服務(wù)接口才是它真正的價值所在。修改一下上面的hello.proto文件:

#hello.proto
syntax = "proto3";
option go_package = ".;main";
package main;
message String {
    string value = 1;
}
service HelloService{
    rpc Hello (String) returns (String);
}

然后安裝go get google.golang.org/grpc/cmd/protoc-gen-go-grpc,然后執(zhí)行命令生成gRPC代碼:protoc --go-grpc_out=. .\hello.proto。此時目錄下會出現(xiàn)一個hello_grpc.pb.go的文件。

Protobuf的protoc編譯器是通過插件機制實現(xiàn)對不同語言的支持。比如protoc命令出現(xiàn)--xxx_out格式的參數(shù),那么protoc將首先查詢是否有內(nèi)置的xxx插件,如果沒有內(nèi)置的xxx插件那么將繼續(xù)查詢當(dāng)前系統(tǒng)中是否存在protoc-gen-xxx命名的可執(zhí)行程序,最終通過查詢到的插件生成代碼。在上面的例子中,--go_out=.會針對hello.proto文件里的message生成相關(guān)代碼,而--go-grpc_out=.會針對hello.proto文件里的service生成相關(guān)代碼。

gRPC

gRPC技術(shù)棧.PNG

安裝gRPC的核心庫:go get google.golang.org/grpc
上文講的,執(zhí)行protoc --go-grpc_out=. .\hello.proto就可以生成對應(yīng)的grpc代碼。gRPC插件會為服務(wù)端和客戶端生成不同的接口:

//客戶端
type helloServiceClient struct {
    cc grpc.ClientConnInterface
}
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
    return &helloServiceClient{cc}
}
type HelloServiceClient interface {
    Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error)
}
func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) {
    out := new(String)
    err := c.cc.Invoke(ctx, "/main.HelloService/Hello", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}
//服務(wù)端
type HelloServiceServer interface {
    Hello(context.Context, *String) (*String, error)
    mustEmbedUnimplementedHelloServiceServer()
}
func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) {
    s.RegisterService(&_HelloService_serviceDesc, srv)
}
type UnimplementedHelloServiceServer struct {
}
func (UnimplementedHelloServiceServer) mustEmbedUnimplementedHelloServiceServer() {}

基于grpc重新實現(xiàn)前面的例子:

//服務(wù)端
package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
)

type HelloServiceImpl struct {
    UnimplementedHelloServiceServer
}

func (p *HelloServiceImpl) Hello(ctx context.Context, args *String) (*String, error) {
    reply := &String{Value: "hello " + args.GetValue()}
    return reply, nil
}

func main() {
    grpcServer := grpc.NewServer()
    RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))
    lis, err := net.Listen("tcp", ":8899")
    if err != nil {
        log.Fatal(err)
    }
    grpcServer.Serve(lis)
}
//----------------------------------------------------------------------
//客戶端
package main

import (
    "context"
    "fmt"
    "log"

    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("localhost:8899", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    clien := NewHelloServiceClient(conn)
    reply, err := client.Hello(context.Background(), &String{
        Value: "Wang",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply.GetValue())
}

gRPC流

RPC是遠程函數(shù)調(diào)用,因此每次調(diào)用的函數(shù)參數(shù)和返回值不能太大,否則將嚴(yán)重影響每次調(diào)用的響應(yīng)時間。因此傳統(tǒng)的RPC方法調(diào)用對于上傳和下載較大數(shù)據(jù)量場景并不適合。為此,gRPC框架針對服務(wù)器端和客戶端分別提供了流特性。

gRPC和TLS

先分別生成服務(wù)端和客戶端的私鑰和證書:

#服務(wù)端
$ openssl genrsa -out server.key 2048
$ openssl req -new -x509 -days 3650 \
    -subj "/C=GB/L=China/O=grpc-server/CN=server.grpc.io" \
    -key server.key -out server.crt
----------------------------------------------------------------------------------
#客戶端
$ openssl genrsa -out client.key 2048
$ openssl req -new -x509 -days 3650 \
    -subj "/C=GB/L=China/O=grpc-client/CN=client.grpc.io" \
    -key client.key -out client.crt

以上命令將生成server.key、server.crt、client.key和client.crt四個文件。其中以.key為后綴名的是私鑰文件,需要妥善保管。以.crt為后綴名是證書文件,也可以簡單理解為公鑰文件,并不需要秘密保存。在subj參數(shù)中的/CN=server.grpc.io表示服務(wù)器的名字為server.grpc.io,在驗證服務(wù)器的證書時需要用到該信息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容