grpc

Go的RPC標準庫

簡單使用

Go語言標準庫(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標準庫請求模板.PNG
Go標準庫的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工具罗捎,然后通過該工具生成代碼观谦,生成代碼放到當前目錄,最后是一系列要處理的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ù)查詢當前系統(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ù)和返回值不能太大沃饶,否則將嚴重影響每次調(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)系作者
  • 序言:七十年代末绩聘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耗啦,更是在濱河造成了極大的恐慌凿菩,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帜讲,死亡現(xiàn)場離奇詭異衅谷,居然都是意外死亡,警方通過查閱死者的電腦和手機舒帮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門会喝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陡叠,“玉大人,你說我怎么就攤上這事肢执⊥髡螅” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵预茄,是天一觀的道長兴溜。 經(jīng)常有香客問我,道長耻陕,這世上最難降的妖魔是什么拙徽? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮诗宣,結(jié)果婚禮上膘怕,老公的妹妹穿的比我還像新娘。我一直安慰自己召庞,他們只是感情好岛心,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著篮灼,像睡著了一般忘古。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诅诱,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天髓堪,我揣著相機與錄音,去河邊找鬼娘荡。 笑死干旁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的它改。 我是一名探鬼主播疤孕,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼商乎,長吁一口氣:“原來是場噩夢啊……” “哼央拖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鹉戚,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲜戒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抹凳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遏餐,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年赢底,在試婚紗的時候發(fā)現(xiàn)自己被綠了失都。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柏蘑。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粹庞,靈堂內(nèi)的尸體忽然破棺而出咳焚,到底是詐尸還是另有隱情,我是刑警寧澤庞溜,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布革半,位于F島的核電站,受9級特大地震影響流码,放射性物質(zhì)發(fā)生泄漏又官。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一漫试、第九天 我趴在偏房一處隱蔽的房頂上張望六敬。 院中可真熱鬧,春花似錦驾荣、人聲如沸觉阅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽典勇。三九已至,卻和暖如春叮趴,著一層夾襖步出監(jiān)牢的瞬間割笙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工眯亦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伤溉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓妻率,卻偏偏與公主長得像乱顾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宫静,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容