GRPC簡(jiǎn)介
A high-performance, open-source universal RPC framework --官網(wǎng)
RPC(remote procedure call 遠(yuǎn)程過程調(diào)用)
- 是什么:提供一套應(yīng)用程序之間可以通信的機(jī)制,使用C/S模型,Client進(jìn)程調(diào)用Server進(jìn)程提供的接口就像是調(diào)用本地函數(shù)一樣。
-
為什么:相比較restful API的優(yōu)勢(shì):
(1)gRPC都使用http底層傳輸協(xié)議建立C/S通信模型,但gRPC使用http2
(2)gRPC可以通過protobuf來(lái)定義由嚴(yán)格約束條件的接口,在提供公共API服務(wù)時(shí),如果不希望client端給我們傳遞亂七八糟的數(shù)據(jù),就可以定義輸入的約束。
(3)gRPC通過protobuf將數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制編碼,減少傳輸數(shù)據(jù)量來(lái)提高傳輸性能。在需要服務(wù)器傳遞大量數(shù)據(jù)的場(chǎng)景下效率更高。
(4)gRPC通過http2.0可以方便的使用streaming模式做流式通信(通常的流式數(shù)據(jù):視頻流) -
怎么用:grpc官網(wǎng)的一個(gè)簡(jiǎn)單Python栗子
(1)在.proto文件中定義服務(wù)。
(2)使用協(xié)議緩沖區(qū)編譯器生成服務(wù)器和客戶端代碼。
(3)使用Python gRPC API為您的服務(wù)編寫一個(gè)簡(jiǎn)單的客戶端和服務(wù)器。 - 安裝:
$ python -m pip install grpcio # 安裝gprc
$ python -m pip install grpcio-tools #安裝grpc-tools
- Python grpc tutorial:The example code for this tutorial is in grpc/grpc/examples/python/route_guide.
QuitStart Python gRPC
下載代碼
$ git clone -b v1.27.0 https://github.com/grpc/grpc
$ cd grpc/examples/python/helloworld
運(yùn)行g(shù)RPC應(yīng)用:用兩個(gè)終端窗口一個(gè)運(yùn)行Server進(jìn)程,一個(gè)運(yùn)行Client進(jìn)程
$ python greeter_server.py # 啟動(dòng)Server
$ python greeter_client.py # 在另外一個(gè)terminal啟動(dòng)client
Server端代碼
"""The Python implementation of the GRPC helloworld.Greeter server."""
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
class Greeter(helloworld_pb2_grpc.GreeterServicer):
# 自定義對(duì)外開放的API接口
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
def serve():
# 使用ThreadPool并發(fā)處理Server任務(wù)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
# 把對(duì)應(yīng)的Greeter任務(wù)添加到rpc server中
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# 這里使用的非安全接口,gRPC支持TLS/SSL安全連接,以及各種鑒權(quán)機(jī)制
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
Client端代碼
"""The Python implementation of the GRPC helloworld.Greeter client."""
from __future__ import print_function
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
# 使用with語(yǔ)法保證channel自動(dòng)close
with grpc.insecure_channel('localhost:50051') as channel:
# 客戶端通過stub來(lái)實(shí)現(xiàn)rpc通信
stub = helloworld_pb2_grpc.GreeterStub(channel)
# 客戶端必須使用定義好的類型,這里是HelloRequest類型
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
修改代碼增添自己的定制API
從proto文件中生成自己的grpc代碼
首先,拷貝examples/protos
文件夾到examples/python/helloworld
文件夾中并修改helloworld.proto
:
// 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;
}
在終端執(zhí)行下面命令,重新生成helloworld_pb2.py
和helloworld_pb2_grpc.py
$ python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/helloworld.proto
可以通過vimdiff對(duì)比一下update前后代碼改動(dòng)
修改greeter_server.py
增加一個(gè)Server函數(shù)SayHelloAgain
class Greeter(helloworld_pb2_grpc.GreeterServicer):
# 自定義對(duì)外開放的API接口
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)
修改greeter_client.py
增加一個(gè)client端的調(diào)用SayHelloAgain函數(shù)的步驟
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
# 使用with語(yǔ)法保證channel自動(dòng)close
with grpc.insecure_channel('localhost:50051') as channel:
# 客戶端通過stub來(lái)實(shí)現(xiàn)rpc通信
stub = helloworld_pb2_grpc.GreeterStub(channel)
# 客戶端必須使用定義好的類型,這里是HelloRequest類型
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
# new
response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
重新在不同的終端運(yùn)行Server和Client:
$ python greeter_server.py # 啟動(dòng)Server
$ python greeter_client.py # 在另外一個(gè)terminal啟動(dòng)client
Proto定義服務(wù)
- 可以在
examples/protos/route_guide.proto
中看到完整的.proto
文件 - 定義Service
service RouteGuide {
// (Method definitions not shown)
}
- 然后,在服務(wù)定義中定義rpc方法,并指定它們的請(qǐng)求和響應(yīng)類型。gRPC可以定義四種服務(wù)方法,所有這些方法都在RouteGuide服務(wù)中看到:
- (1)simple RPC :一種簡(jiǎn)單的RPC,客戶端使用Stub將請(qǐng)求發(fā)送到服務(wù)器,然后等待響應(yīng)返回,就像普通的函數(shù)調(diào)用一樣。
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
- (2)response-streaming RPC:響應(yīng)流式RPC,客戶端向服務(wù)器發(fā)送請(qǐng)求,并獲取 流 以讀取回一系列消息。客戶端從返回的流中讀取,直到?jīng)]有更多消息為止。如示例所示,您可以通過將stream關(guān)鍵字放在響應(yīng)類型之前來(lái)指定響應(yīng)流方法。
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
- (3)request-streaming RPC :請(qǐng)求流式RPC,客戶端編寫消息序列,然后再次使用提供的流將消息發(fā)送到服務(wù)器。客戶端寫完消息后,它將等待服務(wù)器讀取所有消息并返回其響應(yīng)。您可以通過將stream關(guān)鍵字放在請(qǐng)求類型之前來(lái)指定請(qǐng)求流方法。
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
*(4)bidirectionally-streaming RPC:雙向流式RPC,雙方都使用讀寫流發(fā)送一系列消息。這兩個(gè)流是獨(dú)立運(yùn)行的,因此客戶端和服務(wù)器可以按照自己喜歡的順序進(jìn)行讀寫:例如,服務(wù)器可以在寫響應(yīng)之前等待接收所有客戶端消息,或者可以先讀取一條消息再寫入一條消息,或其他一些讀寫組合。每個(gè)流中的消息順序都會(huì)保留。您可以通過在請(qǐng)求和響應(yīng)之前都放置stream關(guān)鍵字來(lái)指定這種類型的方法。
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
-
.proto
文件還包含我們服務(wù)方法中使用的所有請(qǐng)求和響應(yīng)類型的協(xié)議緩沖區(qū)消息類型定義-例如,這是Point消息類型:
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
- 舉個(gè)栗子:
route_guide_server.py
里的class RouteGuideServicer
且數(shù)據(jù)庫(kù)里的數(shù)據(jù)如下:每個(gè)地點(diǎn)都有name和location,其中l(wèi)ocation里包含經(jīng)度和緯度:
{
"location": {
"latitude": 407838351,
"longitude": -746143763
},
"name": "Patriots Path, Mendham, NJ 07945, USA"
}
- (1)simple RPC:簡(jiǎn)單的RPC。
# request:一個(gè)location的經(jīng)度和緯度
# response:是location,包含name
def GetFeature(self, request, context):
feature = get_feature(self.db, request)
if feature is None:
return route_guide_pb2.Feature(name="", location=request)
else:
return feature
- (2)response-streaming RPC:響應(yīng)流式RPC。
# request:一個(gè)地區(qū)的四個(gè)邊的經(jīng)度緯度
# response:落在這個(gè)地區(qū)的所有點(diǎn),按照yield的方式挨個(gè)返回,流式返回
def ListFeatures(self, request, context):
left = min(request.lo.longitude, request.hi.longitude)
right = max(request.lo.longitude, request.hi.longitude)
top = max(request.lo.latitude, request.hi.latitude)
bottom = min(request.lo.latitude, request.hi.latitude)
for feature in self.db:
if (feature.location.longitude >= left and
feature.location.longitude <= right and
feature.location.latitude >= bottom and
feature.location.latitude <= top):
yield feature
- (3)request-streaming RPC:請(qǐng)求流式RPC。
# request:是一個(gè)迭代器iterator,包含一系列l(wèi)ocation
# response: 篩選所有在數(shù)據(jù)庫(kù)里的點(diǎn),返回點(diǎn)的個(gè)數(shù),數(shù)據(jù)庫(kù)內(nèi)點(diǎn)的個(gè)數(shù),按順序走完所有l(wèi)ocation的距離長(zhǎng)度
def RecordRoute(self, request_iterator, context):
point_count = 0
feature_count = 0
distance = 0.0
prev_point = None
start_time = time.time()
for point in request_iterator:
point_count += 1
if get_feature(self.db, point):
feature_count += 1
if prev_point:
distance += get_distance(prev_point, point)
prev_point = point
elapsed_time = time.time() - start_time
return route_guide_pb2.RouteSummary(point_count=point_count,
feature_count=feature_count,
distance=int(distance),
elapsed_time=int(elapsed_time))
- (4)bidirectionally-streaming RPC:雙向流式RPC。
# request: 是一個(gè)迭代器iterator,包含一系列l(wèi)ocation
# response: 根據(jù)每個(gè)location,找到location集里一樣的點(diǎn)
def RouteChat(self, request_iterator, context):
prev_notes = []
for new_note in request_iterator:
for prev_note in prev_notes:
if prev_note.location == new_note.location:
yield prev_note
prev_notes.append(new_note)
啟動(dòng)Server
完成了RouteGuide
的所有API,就可以啟動(dòng)Server了,另外start()
不會(huì)阻塞,所以如果在服務(wù)期間代碼沒有其他事情要做,server則可能需要進(jìn)入睡眠循環(huán)。
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
創(chuàng)建Client
參考完整的Client代碼:
examples/python/route_guide/route_guide_client.py.
- 首先,創(chuàng)建一個(gè)Stub:
channel = grpc.insecure_channel('localhost:50051')
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
-
然后,Call service methods:
對(duì)于返回單個(gè)響應(yīng)的RPC方法(“response-unary”方法),gRPC Python支持同步(阻塞)和異步(非阻塞)控制流語(yǔ)義。對(duì)于響應(yīng)流式RPC方法,調(diào)用立即返回響應(yīng)值的迭代器。調(diào)用該迭代器的next()
方法塊,直到要從該迭代器產(chǎn)生的響應(yīng)變?yōu)榭捎脼橹埂?/li> - (1)Simple RPC:簡(jiǎn)單RPC。GetFeature的同步調(diào)用幾乎與調(diào)用本地方法一樣簡(jiǎn)單。RPC調(diào)用等待服務(wù)器響應(yīng),并且將返回響應(yīng)或引發(fā)異常;GetFeature的異步調(diào)用與此類似,但是就像在線程池中異步調(diào)用本地方法一樣:
# 同步調(diào)用
feature = stub.GetFeature(point)
# 異步調(diào)用
feature_future = stub.GetFeature.future(point)
feature = feature_future.result()
- (2)Response-streaming RPC:響應(yīng)流RPC。調(diào)用響應(yīng)流ListFeatures類似于使用序列類型:
for feature in stub.ListFeatures(rectangle):
- (3)Request-streaming RPC:請(qǐng)求流式RPC。調(diào)用請(qǐng)求流式RecordRoute類似于將迭代器傳遞給本地方法。就像上面的簡(jiǎn)單RPC也返回單個(gè)響應(yīng)一樣,可以同步或異步調(diào)用它:
# 同步
route_summary = stub.RecordRoute(point_iterator)
# 異步
route_summary_future = stub.RecordRoute.future(point_iterator)
route_summary = route_summary_future.result()
- (4)Bidirectional streaming RPC:雙向流RPC。調(diào)用雙向流RouteChat具有服務(wù)流和響應(yīng)流語(yǔ)義的組合(在服務(wù)端就是這種情況):
for received_route_note in stub.RouteChat(sent_route_note_iterator):
參考:
gRPC 官網(wǎng)
gRPC python QuickStart
gRPC python basic tutorial