RPC
試想這樣一種場景,一個復雜系統中的兩個模塊之前需要互相調用,一般的做法是什么?
可能這兩個模塊是跑在同一個進程上,那么通信起來其實是非常方便的,也有可能這兩個模塊分別是跑在不同的進程之上,那么就涉及到復雜一點的跨進程通信的技術了。但這些都是模塊部署在同一機器下的情景,大家想象起來也會比較容易。
更加深入一些,如果兩個模塊跑在不同的機器之間,那么模塊之前的調用如何實現呢?這就需要使用RPC技術了。
RPC(Remote Procedure Call)— 遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通信程序之間攜帶信息數據。在OSI網絡通信模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分布式多程序在內的應用程序更加容易。
RPC采用客戶端/服務器模式。請求程序就是一個客戶端,而服務提供程序就是一個服務器。首先,客戶端調用進程發送一個有進程參數的調用信息到服務進程,然后等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達為止。當一個調用信息到達,服務器獲得進程參數,計算結果,發送答復信息,然后等待下一個調用信息,最后,客戶端調用進程接收答復信息,獲得進程結果,然后調用執行繼續進行。
簡單來說RPC需要server端和client端,server端定義一些函數,client端通過網絡請求去調用這些函數拿到返回值。server端和client端跑在不同的機器上,結合微服務的概念就是server端就是一個獨立的微服務,其他微服務需要通過啟動client端來調用該微服務提供的服務。
gRPC
gRPC 一開始由 Google 開發,是一款語言中立、平台中立、開源的遠程過程調用(RPC)系統。
在 gRPC 里客戶端應用可以像調用本地對象一樣直接調用另一台不同的機器上服務端應用的方法,使得您能夠更容易地創建分布式應用和服務。與許多 RPC 系統類似,gRPC 也是基於以下理念:定義一個服務,指定其能夠被遠程調用的方法(包含參數和返回類型)。在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。在客戶端擁有一個存根能夠像服務端一樣的方法。
組成
典型的grpc實現有兩端組成,分別是
- server
- client
gRPC的特性
- 由於client和server需要通過網絡進行消息的傳遞,那么網絡協議成了grpc里重要的一環。grpc協議是HTTP/2,這是一種優化過的http協議,實現了連接多路復用、雙向流、服務器推送、請求優先級、首部壓縮等機制。可以節省帶寬、降低TCP鏈接次數、節省CPU,幫助移動設備延長電池壽命等。
- 服務端向外提供了一些可供調用的函數,這些函數的原型通過ProtoBuf協議來進行定義。ProtoBuf是由Google開發的一種數據序列化協議(類似於XML、JSON、hessian)。ProtoBuf能夠將數據進行序列化,並廣泛應用在數據存儲、通信協議等方面。壓縮和傳輸效率高,語法簡單,表達力強。
- 支持多種編程語言。比如支持golang/java/c++/ruby/python/nodejs等。
gRPC的優點
- 使用protobuf進行消息的序列化,壓縮率高,性能好,畢竟壓縮的越小在網絡上傳播的速度就相對會更快一點
- 序列化反序列化直接對應程序中的數據類,不需要解析后在進行映射,其實除了可讀性差之外,pb的使用方式跟json基本都差不多了
- 支持向前兼容和向后兼容,升級比較簡單
- 支持多語言
典型的gRPC實現
典型的gRPC實現有3個部分,分別是
- 服務定義,使用protobuf的語法
- server端實現,可以使用任意支持的語言
- client端實現,可以使用任意支持的語言
這里我們簡單演示一下如何使用python實現簡單的grpc server和client
服務定義
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (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;
}
server端實現
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message='Hello again, %s!' % request.name)
...
client端實現
def run():
channel = grpc.insecure_channel('localhost:50051')
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
測試gRPC的server
其實跟接口測試的概念差不多,使用client stub去調用server,然后進行斷言就好了。簡而言之就是寫代碼去調用server端提供的函數,然后做斷言
測試client
這一部分對大家來說可能不太好想象,有一種做法是mock client,實現client的一系列調用server的mock方法,然后把client代入正常的業務邏輯,最后做邏輯或流程正確與否的判斷。
舉個例子,比如有個微服務使用client調用了a和b兩個函數,那么就mock掉a和b的client端實現,最后在正常的業務邏輯結束之后,斷言client先調用了a再調用了b。這種mock的方式之前在做單元測試的時候非常的普遍,一般是用來mock掉網絡請求或者是數據庫連接,用在rpc的client測試上就顯得比較有意思了。
性能測試
推薦使用ghz。
ghz · gRPC benchmarking and load testing tool
監控
一般可以使用下面的兩種方案
- OpenCensus
- Prometheus
Tracing
因為微服務之前的調用鏈路很復雜,所以需要使用tracing來進行調用鏈的跟蹤。這里可以簡單的使用OpenCensus Jaeger exporter來實現。
grpc gateway
在測試和調式的時候,每次寫client去調用server其實是一件比較麻煩的事情,grpc gateway提供了一種簡單的方式把grpc轉成restful形式的接口,這樣就可以直接使用postman等工具進行調試和測試了。