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的初衷:提供一個移動端到伺服器的解決方案。去到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
型別。這裡HelloRequest
和HelloReply
就是普通的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