RPC
RPC(Remote Procedure Call: 遠程過程調用)是一個計算機通信協議,該協議允許運行於一台計算機的程序調用另一個地址空間(通常為一個開放網絡的一台計算機)的子程序,而程序員就像調用本地程序一樣,無需額外地為這個交互作用編程(無需關注細節)。
gRPC
gRPC 一開始由 google 開發,是一款語言中立、平台中立、開源的遠程過程調用(RPC)系統。(http://doc.oschina.net/grpc)
在 gRPC 里客戶端應用可以像調用本地對象一樣直接調用另一台不同的機器上服務端應用的方法,使得您能夠更容易地創建分布式應用和服務。與許多 RPC 系統類似,gRPC 也是基於以下理念:定義一個服務,指定其能夠被遠程調用的方法(包含參數和返回類型)。在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。在客戶端擁有一個存根能夠像服務端一樣的方法。
gRPC 默認使用 protocol buffers,這是 Google 開源的一套成熟的結構數據序列化機制(當然也可以使用其他數據格式如 JSON)。
使用gRPC分為三步
- 編寫.proto文件
- 利用工具將.proto文件生成對應語言的代碼
- 根據生成的代碼編寫服務端和客戶端的代碼
開始之前
首先我們需要安裝將.proto文件生成對應代碼的工具,下載地址(https://github.com/protocolbuffers/protobuf/releases),下載你對應操作系統的壓縮包即可。下載完成后解壓將其bin目錄下的可執行文件放入環境變量中的文件夾即可。
此外,我們還需要安裝對應語言的插件,比如
go語言插件安裝方式
go get -u github.com/golang/protobuf/protoc-gen-go
python插件安裝方式
pip install grpcio
pip install protobuf
pip install grpcio_tools
接下來我將以一個例子來做演示介紹如何在go中使用gRPC,注意我將采用go module的方式來編寫這個demo,
首先我們在一個你喜歡的文件夾下面新建一個文件夾命名為hello_grpc
,然后在hello_grpc
文件夾下新建一個go.mod文件並寫入一下內容
module "hello_grpc"
然后用你喜歡的IDE打開這個文件夾,進行之后的操作
1. 編寫.proto文件
我們在項目的根目錄下新建名為pb
的文件夾,然后新建名為hello_grpc.proto
的文件,寫入如下內容,這是一個官方的例子
syntax = "proto3";
package service;
option go_package = ".;hello_grpc";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
其中第一行指定了我們使用 protocol buffers的版本
下面我們定義了包的名稱,這將成為后面我們生成的go語言的代碼的包名
然后我們定義了一個服務名為Greeter,其中定義了一個函數SayHello它的參數定義在HelloRequest,返回值定義在HelloReply
關於proto的服務,一共有4種類型,此例子中是最簡單的一種
-
簡單 RPC
客戶端使用存根發送請求到服務器並等待響應返回,就像平常的函數調用一樣。
rpc GetFeature(Point) returns (Feature) {}
-
服務器端流式 RPC
客戶端發送請求到服務器,拿到一個流去讀取返回的消息序列。 客戶端讀取返回的流,直到里面沒有任何消息。從例子中可以看出,通過在 響應 類型前插入
stream
關鍵字,可以指定一個服務器端的流方法。rpc ListFeatures(Rectangle) returns (stream Feature) {}
-
客戶端流式 RPC
客戶端寫入一個消息序列並將其發送到服務器,同樣也是使用流。一旦客戶端完成寫入消息,它等待服務器完成讀取返回它的響應。通過在 請求 類型前指定
stream
關鍵字來指定一個客戶端的流方法。rpc RecordRoute(stream Point) returns (RouteSummary) {}
-
雙向流式 RPC
是雙方使用讀寫流去發送一個消息序列。兩個流獨立操作,因此客戶端和服務器可以以任意喜歡的順序讀寫:比如, 服務器可以在寫入響應前等待接收所有的客戶端消息,或者可以交替的讀取和寫入消息,或者其他讀寫的組合。 每個流中的消息順序被預留。你可以通過在請求和響應前加
stream
關鍵字去制定方法的類型。rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
2. 生成對應語言的代碼
在我們項目的根目錄下新建service
目錄(這將用來存放我們生成的golang代碼),然后打開終端,輸入命令
protoc -I pb/ pb/hello_grpc.proto --go_out=plugins=grpc:service
-I
后面指定proto文件存放目錄,和proto文件
--go_out=plugins=grpc:
后面指定生成go代碼存放的目錄
檢查在service目錄下是否成功生成一個名為hello_grpc.pb.go
的文件
打開這個文件發現抱錯,原因是我們的項目中還沒有安裝相應的包,輸入以下命令安裝
go mod tidy
3.編寫服務端和客戶端的代碼
服務端
在根目錄下新建文件夾server
然在這個文件夾下新建server.go
,寫入以下內容
package main
import (
"context"
"fmt"
"hello_grpc/service"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"net"
)
type server struct {}
func (s *server) SayHello(ctx context.Context, in *service.HelloRequest) (*service.HelloReply, error) {
return &service.HelloReply{Message: "hello " + in.Name}, nil
}
func main() {
// 監聽本地端口
lis, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Printf("監聽端口失敗: %s", err)
return
}
// 創建gRPC服務器
s := grpc.NewServer()
// 注冊服務
service.RegisterGreeterServer(s, &server{})
reflection.Register(s)
err = s.Serve(lis)
if err != nil {
fmt.Printf("開啟服務失敗: %s", err)
return
}
}
我們這里先定義了一個結構體,該結構體需要實現GreeterServer
這個接口(見生成的代碼,如下)
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
服務端
同理在項目根目錄下新建client/client.go
寫入
package main
import (
"context"
"fmt"
"hello_grpc/service"
"google.golang.org/grpc"
)
func main(){
// 連接服務器
conn, err := grpc.Dial(":8080", grpc.WithInsecure())
if err != nil {
fmt.Printf("連接服務端失敗: %s", err)
return
}
defer conn.Close()
// 新建一個客戶端
c := service.NewGreeterClient(conn)
// 調用服務端函數
r, err := c.SayHello(context.Background(), &service.HelloRequest{Name: "horika"})
if err != nil {
fmt.Printf("調用服務端代碼失敗: %s", err)
return
}
fmt.Printf("調用成功: %s", r.Message)
}
4. 運行
-
先運行服務端
go run server/server.go
-
開新的終端運行客戶端
go run client/client.go
-
觀察客戶端輸出
5. 番外篇:編寫跨語言調用
以python為例,這里就不編寫python的服務端了,直接編寫python的客戶端調用go的服務端
注意:你是否安裝python的插件?
pip install grpcio
pip install protobuf
pip install grpcio_tools
生成python代碼
在項目跟目錄下新建文件夾python
然后在根目錄下打開終端輸入命令
python -m grpc_tools.protoc -I pb/ --python_out=python/ --grpc_python_out=python/ pb/hello_grpc.proto
在剛剛新建的文件夾下查看是否多出來了兩個文件hello_grpc_pb2.py
和hello_grpc_pb2_grpc.py
編寫python客戶端代碼
在python文件夾中新建client.py寫入
import logging
import grpc
import hello_grpc_pb2
import hello_grpc_pb2_grpc
def run():
with grpc.insecure_channel('localhost:8080') as channel:
stub = hello_grpc_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(hello_grpc_pb2.HelloRequest(name='horika'))
print("調用成功: {}!".format(response.message))
if __name__ == '__main__':
logging.basicConfig()
run()
運行go的服務端,然后運行python客戶端查看輸出