本文旨在說明 spring-boot-starter-grpc 框架與 Python 之間跨語言 RPC 調(diào)用的友好實(shí)現(xiàn)。閱讀本文前,請?jiān)敿?xì)閱讀 wiki文檔饰迹,以便了解 spring-boot-starter-grpc
的工作原理。
Java Server & Client
模塊說明:
samples-interface 定義了不同的接口,接口的定義中指明了序列化方式赦颇,具體包含
Sofa-Hessian
、ProtoStuff
赴涵、FastJSON
samples-server 依賴
samples-interface
模塊媒怯,并實(shí)現(xiàn)了該模塊中定義的所有接口samples-client 依賴
samples-interface
,遠(yuǎn)程調(diào)用samples-server
的實(shí)現(xiàn)類髓窜,并提供了 HTTP 接口從外部調(diào)用 RPC扇苞,方便測試
grpc-python 準(zhǔn)備工作
0、序列化方式選擇
使用 JSON 文本
1寄纵、安裝 Python 相關(guān)庫
pip install grpcio
pip install protobuf
pip install grpcio-tools
2鳖敷、Python 工程相關(guān)目錄結(jié)構(gòu)說明
-
com/anoyi/grpc/facade/service/UserServiceByFastJSON.py
對應(yīng) Java 模塊samples-facade
中定義的接口 UserServiceByFastJSON -
client.py
python grpc 客戶端,用于遠(yuǎn)程調(diào)用服務(wù)端的方法 -
server.py
python grpc 服務(wù)端程拭,用于提供方法實(shí)現(xiàn)定踱,供客戶端調(diào)用 -
service.proto
通用的 proto 文件,與 spring-boot-starter-grpc 中定義的一致 -
service_pb2.py
和service_pb2_grpc.py
由工具grpcio-tools
根據(jù)service.proto
生成恃鞋,生成方式如下:
python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. ./service.proto
Python Server & Java Client
跨語言框架級對接中崖媚,使用了通用的 service.proto
,所以服務(wù)端使用反射機(jī)制來將請求體映射到對應(yīng)的具體實(shí)現(xiàn)方法恤浪,server.py
具體實(shí)現(xiàn)示例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import importlib
import json
import time
import grpc
from concurrent import futures
import service_pb2
import service_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
'''
公共服務(wù)
'''
class CommonService(service_pb2_grpc.CommonServiceServicer):
def handle(self, request, context):
# 將請求信息轉(zhuǎn)為 json 格式
_request = json.loads(str(request.request))
# 反射加載模塊及方法
_module = importlib.import_module(_request['clazz'])
_method = getattr(_module, _request['method'])
# 執(zhí)行方法
args = _request.get('args')
if not args:
_response = "{'status': 0, 'result': %s}" % _method()
else:
_response = "{'status': 0, 'result': %s}" % _method(_request['args'])
return service_pb2.Response(response=bytes(_response))
'''
服務(wù)端
'''
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
service_pb2_grpc.add_CommonServiceServicer_to_server(CommonService(), server)
server.add_insecure_port('127.0.0.1:6565')
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
'''
啟動(dòng)
'''
if __name__ == '__main__':
print 'server running, listen on 6565'
serve()
client.py
示例:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import grpc
import json
import service_pb2
import service_pb2_grpc
'''
請求遠(yuǎn)程方法-添加用戶
'''
def insert():
user = '{"id":1,"name":"anoyi","age":20}'
_request = {
'clazz': 'com.anoyi.grpc.facade.service.UserServiceByFastJSON',
'method': 'insert',
'args': [user]
}
_request_json = json.dumps(_request, ensure_ascii=False)
with grpc.insecure_channel('127.0.0.1:6565') as channel:
stub = service_pb2_grpc.CommonServiceStub(channel)
response = stub.handle(service_pb2.Request(request=bytes(_request_json), serialize=3))
print("received: " + response.response)
'''
請求遠(yuǎn)程方法-查詢用戶
'''
def findAll():
_request = {
'clazz': 'com.anoyi.grpc.facade.service.UserServiceByFastJSON',
'method': 'findAll'
}
_request_json = json.dumps(_request, ensure_ascii=False)
with grpc.insecure_channel('127.0.0.1:6565') as channel:
stub = service_pb2_grpc.CommonServiceStub(channel)
response = stub.handle(service_pb2.Request(request=bytes(_request_json), serialize=3))
print("received: " + response.response)
'''
啟動(dòng)
'''
if __name__ == '__main__':
print '--- RPC: insert ---'
insert()
print '--- RPC: findAll ---'
findAll()
UserServiceByFastJSON.py
示例:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import json
userDatas = {}
def findAll():
data = json.dumps(userDatas.values(), ensure_ascii=False)
return data.encode("utf-8")
def insert(args):
user = json.loads(args[0])
userDatas[str(user['id'])] = user
return {}
def deleteById(args):
userDatas.pop(str(args[0]))
return {}
Python Server 與 Java Client 對接
1畅哑、運(yùn)行 server.py
python server.py
常見問題①
Traceback (most recent call last):
File "server.py", line 10, in <module>
import service_pb2
File "/Users/admin/code/python/python-grpc/service_pb2.py", line 22, in <module>
serialized_pb=_b('\n\rservice.proto\"-\n\x07Request\x12\x11\n\tserialize\x18\x01 \x01(\x05\x12\x0f\n\x07request\x18\x02 \x01(\x0c\"\x1c\n\x08Response\x12\x10\n\x08response\x18\x01 \x01(\x0c\x32\x30\n\rCommonService\x12\x1f\n\x06handle\x12\x08.Request\x1a\t.Response\"\x00\x42\x1e\n\rcom.anoyi.rpcB\x0bGrpcServiceP\x00\x62\x06proto3')
TypeError: __init__() got an unexpected keyword argument 'serialized_options'
解決方案:修改 service_pb2.py
文件,將所有的 serialized_options
替換為 options
常見問題②
Traceback (most recent call last):
File "server.py", line 11, in <module>
import service_pb2_grpc
File "/Users/admin/code/python/python-grpc/service_pb2_grpc.py", line 8
SyntaxError: Non-ASCII character '\xe5' in file /Users/admin/code/python/python-grpc/service_pb2_grpc.py on line 8, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
解決方案:修改 service_pb2_grpc.py
水由,在文件頭部添加如下內(nèi)容:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
2敢课、啟動(dòng) Java Client,運(yùn)行 samples-client 的 Application
3、調(diào)用測試
Python Client 與 Java Server 對接
1直秆、啟動(dòng) Java Server濒募,運(yùn)行 samples-server 的 Application
2、運(yùn)行 client.py
python client.py
3圾结、測試瑰剃,查看 python client 的運(yùn)行日志即可
說明:python 字符編碼復(fù)雜,中文支持請自行實(shí)現(xiàn)筝野。
Python Client 與 Python Server 對接
1晌姚、 運(yùn)行 server.py
python server.py
2、 運(yùn)行 client.py
python client.py
3歇竟、測試挥唠,查看 python client 的運(yùn)行日志即可