1. 程式人生 > >gRPC初體驗

gRPC初體驗

gRPC是由Google主導開發的RPC框架,使用HTTP/2協議並用ProtoBuf作為序列化工具。其客戶端提供Objective-C、Java介面,伺服器側則有Java、Golang、C++等介面,從而為移動端(iOS/Androi)到伺服器端通訊提供了一種解決方案。 當然在當下的環境下,這種解決方案更熱門的方式是RESTFull API介面。該方式需要自己去選擇編碼方式、伺服器架構、自己搭建框架(JSON-RPC)。gRPC官方對REST的聲音是:

  • 和REST一樣遵循HTTP協議(明確的說是HTTP/2),但是gRPC提供了全雙工流
  • 和傳統的REST不同的是gRPC使用了靜態路徑,從而提高效能
  • 用一些格式化的錯誤碼代替了HTTP的狀態碼更好的標示錯誤

至於是否要選擇用gRPC。對於已經有一套方案的團隊,可以參考下。如果是從頭來做,可以考慮下gRPC提供的從客戶端到伺服器的整套解決方案,這樣不用客戶端去實現http的請求會話,JSON等的解析,伺服器端也有現成的框架用。從15年3月到現在gRPC也發展了一年了,慢慢趨於成熟。下面我們就以gRPC的Golang版本看下其在golang上面的表現。至於服務端的RPC,感覺golang標準庫的RPC框架基本夠用了,沒必要再去用另一套方案。

1. 安裝protobuf

雖然gRPC也支援protobuf2.x,但是建議還是使用protobuf3.x,儘管還沒有正式版本,不過golang版本基本沒有什麼問題,另外3.x官方支援了Objective-C,這也是我們使用gRPC的初衷:提供一個移動端到伺服器的解決方案。去到

Protocol Buffers下載最新版本(Version3.0.0 beta2),然後解壓到本地。本地需要已經安裝好autoconf automake libtool.rpm系列(fedora/centos/redheat)可以用yum安裝。Mac上可以用brew進行安裝

brew install autoconf automake libtool

然後執行

./configure --prefix=your_pb_install_path

接著

make 
make install
set your_pb_install_path to your $PATH

檢查是否安裝完成

protoc --version
libprotoc 3.0.0

然後安裝golang protobuf直接使用golang的get即可

go get -u github.com/golang/protobuf/proto // golang protobuf 庫
go get -u github.com/golang/protobuf/protoc-gen-go //protoc --go_out 工具

2. 安裝gRPC-go

gRPC-go可以通過golang 的get命令直接安裝,非常方便。

go get google.golang.org/grpc

這裡大家可能比較奇怪,為什麼gRPC-go在github的地址是"https://github.com/grpc/grpc-go",但是為什麼要用“google.golang.org/grpc”進行安裝呢?應該grpc原本是google內部的專案,歸屬golang,就放在了google.golang.org下面了,後來對外開放,又將其遷移到github上面了,又因為golang比較坑爹的import路徑規則,所以就都沒有改路徑名了。

但是這樣就有個問題了。要如何去管理版本呢?這個目前我還沒有什麼比較好的方法,希望知道的朋友一起分享下。目前想到一個方法是手動下載某個版本,然後寫個指令碼統一修改程式碼中的import裡面的路徑.

3. 示例程式

3.1 protobuf

該示例源自gRPC-go的examples的helloworld。先看PB的描述:

syntax = "proto3";

option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

這裡定義了一個服務Greeter,其中有個API SayHello。其接受引數為HelloRequest型別,返回HelloReply型別。這裡HelloRequestHelloReply就是普通的PB定義

服務定義為:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

service定義了一個server。其中的介面可以是四種類型

  • rpc GetFeature(Point) returns (Feature) {}
    類似普通的函式呼叫,客戶端傳送請求Point到伺服器,伺服器返回相應Feature.
  • rpc ListFeatures(Rectangle) returns (stream Feature) {}
    客戶端發起一次請求,伺服器端返回一個流式資料,比如一個數組中的逐個元素
  • rpc RecordRoute(stream Point) returns (RouteSummary) {}
    客戶端發起的請求是一個流式的資料,比如陣列中的逐個元素,伺服器返回一個相應
  • rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    客戶端發起的請求是一個流式資料,比如陣列中的逐個元素,二伺服器返回的也是一個類似的資料結構

使用protoc命令生成相關檔案:

protoc --go_out=plugins=grpc:. helloworld.proto
ls
helloworld.pb.go    helloworld.proto

生成對應的pb.go檔案。這裡用了plugins選項,提供對grpc的支援,否則不會生成Service的介面。

3.2 伺服器端程式

然後編輯伺服器端程式:

package main

import (
    "log"
    "net"

    pb "your_path_to_gen_pb_dir/helloworld"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    s.Serve(lis)
}

這裡首先定義一個server結構,然後實現SayHello的介面,其定義在“your_path_to_gen_pb_dir/helloworld”

SayHello(context.Context, *HelloRequest) (*HelloReply, error)

然後呼叫grpc.NewServer() 建立一個server s。接著註冊這個server s到結構server上面 pb.RegisterGreeterServer(s, &server{}) 最後將建立的net.Listener傳給s.Serve()。就可以開始監聽並服務了,類似HTTP的ListenAndServe。

3.3 客戶端程式

客戶端程式:

package main

import (
    "log"
    "os"

    pb "your_path_to_gen_pb_dir/helloworld"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

這裡通過pb.NewGreeterClient()傳入一個conn建立一個client,然後直接呼叫client上面對應的伺服器的介面

SayHello(context.Context, *HelloRequest) (*HelloReply, error)

介面,返回*HelloReply 物件。

先執行伺服器,在執行客戶端,可以看到。

./greeter_server &

./greeter_client
2016/03/10 21:42:19 Greeting: Hello world