什么是拦截器?在构建gRPC应用程序时,无论是客户端应用程序,还是服务器端应用程序,在远程方法执行之前或之后,都免不了需要执行一些通用逻辑。在gRPC中,可以使用拦截器的扩展机制来拦截RPC的执行以满足特定的需求,如日志、认证、性能度量指标等。根据所拦截的RPC调用的类型,gRPC拦截器可以分为两类。对于一元RPC,可以使用一元拦截器(unary interceptor);对于流RPC,则可以使用流拦截器(streaming interceptor)。这些拦截器既可以用在gRPC服务器端,也可以用在gRPC客户端。

​func(ctx context.Context, req interface{], info *UnaryServerInfo, handler UnaryHandler) (resp insterface{], err error)​​grpc.NewServer(grpc.UnaryInterceptor(xxxServerInterceptor))​
​func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error​


​func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) ERROR​
​func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)​

etcd 提供了丰富的 metrics、日志、请求行为检查等机制,可记录所有请求的执行耗时及错误码、来源 IP 等,也可控制请求是否允许通过,比如 etcd Learner 节点只允许指定接口和参数的访问,帮助大家定位问题、提高服务可观测性等,而这些特性是怎么非侵入式的实现呢?etcd server 定义了如下的 Service KV 和 Range 方法,启动的时候它会将实现 KV 各方法的对象注册到 gRPC Server,并在其上注册对应的拦截器。拦截器提供了在执行一个请求前后的 hook 能力,除了我们上面提到的 debug 日志、metrics 统计、对 etcd Learner 节点请求接口和参数限制等能力,etcd 还基于它实现了以下特性:

  • 要求执行一个操作前集群必须有 Leader;
  • 请求延时超过指定阈值的,打印包含来源 IP 的慢查询日志 (3.5 版本)。

server 收到 client 的 Range RPC 请求后,根据 ServiceName 和 RPC Method 将请求转发到对应的 handler 实现,handler 首先会将上面描述的一系列拦截器串联成一个执行,在拦截器逻辑中,通过调用 KVServer 模块的 Range 接口获取数据。

如下函数就是将一元拦截器和流拦截器注册到创建gRPC服务器端的过程。newUnaryInterceptor、newLogUnaryInterceptor和newStreamInterceptor定义在server/etcdserver/api/v3rpc/interceptor.go文件中,下面我们描述一下这些拦截器。

newLogUnaryInterceptor函数生成服务器端一元拦截器,它没有前置处理,但是有后置处理逻辑。如果打开debug日志级别或者请求延时超过指定阈值的,则后置处理逻辑会获取统计信息并打印。

UnaryInterceptor主要是对etcd Learner 节点请求接口和参数限制进行限制,检测执行一个操作前集群必须有 Leader。

​streams map[grpc.ServerStream]struct{}​

参考文章:gRPC与云原生应用开发 以Go和Java为例