有一个开源项目叫 go-grpc-middleware,可以去看一下,比如认证、日志、监控等服务,可以直接和流行的组件对接。不过一般都针对于服务端,客户端的话更常用的是重试机制。但是对于开发微服务而言,更重要的还是搞懂背后的机制。
grpc 拦截器 - Python看完了 Go 的拦截器,再来看看的 Python 的拦截器,Python 实现拦截器的话比 Go 还要简单一些。
import grpc pb2, pb2_grpc = grpc.protos_and_services("hello.proto") class Hello(pb2_grpc.HelloServicer): def SayHello(self, request, context): if "flag" not in globals(): return pb2.HelloResponse(info={"error": \'缺少名为 "matsuri" 的 key,或者该 key 对应的 value 不正确\'}) globals().pop("flag") return pb2.HelloResponse(info={"name": "神乐七奈"}) # 定义拦截器,我们需要继承 grpc.ServerInterceptor,这是一个抽象基类,我们必须实现里面的一个方法,否则是会报错的 class MyInterceptor(grpc.ServerInterceptor): def intercept_service(self, continuation, handler_call_details): """continuation 等价于 Go 里面的 handler handler_call_details 是一个 grpc._server._HandlerCallDetails(继承了 namedtuple) 里面有两个属性: method 表示要调用的方法、invocation_metadata 就是客户端传递的 metadata """ md = handler_call_details.invocation_metadata for k, v in md: if k == "matsuri" and v == "fubuki": globals()["flag"] = 1 # 我们必须通过 continuation(handler_call_details) 来调用方法,直接返回一个 Response 是不允许的 # 所以这一点不如 Go,因此我们只能通过设置一个全局变量的方式来解决问题 return continuation(handler_call_details) if __name__ == \'__main__\': from concurrent.futures import ThreadPoolExecutor # 实例化的时候将拦截器放到里面来 grpc_server = grpc.server(ThreadPoolExecutor(max_workers=4), interceptors=(MyInterceptor(),)) pb2_grpc.add_HelloServicer_to_server(Hello(), grpc_server) grpc_server.add_insecure_port("127.0.0.1:22222") grpc_server.start() grpc_server.wait_for_termination()上面是服务端,再来看看客户端:
import grpc pb2, pb2_grpc = grpc.protos_and_services("hello.proto") channel = grpc.insecure_channel("127.0.0.1:22222") client = pb2_grpc.HelloStub(channel=channel) response = client.SayHello( pb2.HelloRequest() ) print(response.info) # {\'error\': \'缺少名为 "matsuri" 的 key,或者该 key 对应的 value 不正确\'} response, _ = client.SayHello.with_call( pb2.HelloRequest(), metadata=(("matsuri", "fubuki"),) ) print(response.info) # {\'name\': \'神乐七奈\'}因此这就是 Python 的验证器的逻辑,目前是通过设置全局变量的方式来曲线救国,也许会有更优雅的逻辑。
grpc 的错误处理机制下面我们来介绍一下 grpc 的错误处理机制,你可以自己尝试一下,如果服务端在调用方法的时候发生了错误,那么控制台是不会有任何显示的。而客户端只会得到一个服务调用失败,告诉我们服务端出现了错误,显然这是不行的;以及 Go 的异常处理和 Python 还不一样,那么在两者进行交互的时候要如何处理异常,这些细节我们都要把握到。
首先 gRPC 给我们内置了一些状态码,这些状态码如下,它们都是一个整型:
ok,值为 0,代表成功调用
CANCELLED,值为 1,调用操作被取消,特别是调用方
UNKNOWN,值为 2,代表未知错误
INVALID_ARGUMENT,值为 3,代表参数不合法
DEADLINE_EXCEEDED,值为 4,代表操作完成前就已经超时了
NOT_FOUND,值为 5,方法不存在
ALREADY_EXISTS,值为 6,客户端尝试创建一个已经存在的方法
PERMISSION_DENIED,值为 7,客户端尝试调用一个没有权限调用的方法
UNAUTHEDTICATED,值为 16,客户端在调用某个方法时没有经过认证
RESOURCE_EXHAUSTED,值为 8,资源被耗尽
FAILED_PRECONDITION,值为 9,前置条件不满足,比如删除一个目录,但是目录非空
ABORTED,值为 10,操作被取消,特别是由于并发所导致的问题
OUT_OF_RANGE,值为 11,范围越界,比如读取一个文件结尾之后的部分
UNIMPLEMENTED,值为 12,服务端定义了、但是没有实现此方法
INTERNAL,值为 13,内部错误
UNAVAILABLE,值为 14,服务不可用
DATA_LOSS,值为 15,数据丢失
我们来演示一下,服务端如何返回异常,客户端如何捕捉异常,proto 文件和原来保持一致,但是为了更好的演示,我们增加一个参数:
syntax = "proto3"; option go_package = ".;yoyoyo"; // 对 Python 无影响 message HelloRequest { string name = 1; } message HelloResponse { map<string, string> info = 1; } service Hello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }