前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Interceptor拦截器 -- gRPC生态里的中间件

Interceptor拦截器 -- gRPC生态里的中间件

作者头像
KevinYan
发布2021-05-11 15:09:13
1.6K0
发布2021-05-11 15:09:13
举报
文章被收录于专栏:网管叨bi叨

什么是拦截器

gRPC的拦截器(interceptor)类似各种Web框架里的请求中间件,请求中间件大家都知道是利用装饰器模式对最终处理请求的handler程序进行装饰,这样中间件就可以在处理请求前和完成处理后这两个时机上,拦截到发送给 handler 的请求以及 handler 返回给客户端的响应 。

中间件的最大的用处是可以把一些 handler 的前置和后置操作从 handler 程序中解耦出来,比如最常见的记录响应时长、记录请求和响应数据日志等操作往往是通过中间件程序实现的。

与 Web 框架的中间件同理,可以对gRPC的请求和响应进行拦截处理,而且既可以在客户端进行拦截,也可以对服务器端进行拦截。利用拦截器,可以对gRPC进行很好的扩展,把一些业务逻辑外的冗余操作从 handler 中抽离,提升项目的开发效率和扩展性。

怎么使用拦截器

gRPC的服务器和客户端都是分别可以添加一个单向调用 (Unary) 的拦截器和流式调用 (Stream) 的拦截器。

这两种调用方式的区别可以理解为HTTP和WebSocket的区别

对于客户端的单向调用的拦截,只需定义一个 UnaryClientInterceptor 方法:

代码语言:javascript
复制
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error

而客户端流式调用的拦截,则需要定义一个 StreamClientInterceptor 方法:

代码语言:javascript
复制
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)

同理,对于gRPC的服务端也有这两种调用的拦截器方法,分别是 UnaryServerInterceptorStreamServerInterceptor

代码语言:javascript
复制
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

拦截器应用

下面简单演示一下,怎么用客户端和服务端拦截器来实现gRPC客户端调用日志,和gRPC服务器访问日志的。

首先我们定义一下客户端单向调用的拦截器方法:

代码语言:javascript
复制
func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
 start := time.Now()
 defer func() {
  in, _ := json.Marshal(req)
  out, _ := json.Marshal(reply)
  inStr, outStr := string(in), string(out)
  duration := int64(time.Since(start) / time.Millisecond)

  log.Println("grpc", method, "in", inStr, "out", outStr, "err", err, "duration/ms", duration, "remote_server", remoteServer)

 }()

 return invoker(ctx, method, req, reply, cc, opts...)
}

创建客户端的时候应用上这个方法:

代码语言:javascript
复制
var client routeGuideClient

func init() {
 var err error
 client.cc, err = grpc.Dial(
  "127.0.0.1:12305",
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(UnaryClientInterceptor),
 )
 if err != nil {
  panic(err)
 }

}

routeguide 这个服务名是自己起的,其实就是拿的 grpc 官方的示例稍微改动了一下做的试验。

接下来定义一个服务器端的拦截器:

代码语言:javascript
复制
func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
 remote, _ := peer.FromContext(ctx)
 remoteAddr := remote.Addr.String()

 in, _ := json.Marshal(req)
 inStr := string(in)
 log.Println("ip", remoteAddr, "access_start", info.FullMethod, "in", inStr)

 start := time.Now()
 defer func() {
  out, _ := json.Marshal(resp)
  outStr := string(out)
  duration := int64(time.Since(start) / time.Millisecond)
  log.Println("ip", remoteAddr, "access_end", info.FullMethod, "in", inStr, "out", outStr, "err", err, "duration/ms", duration)
 }()

 resp, err = handler(ctx, req)

 return
}

在服务器启动时应用上这个单向调用的拦截器:

代码语言:javascript
复制

var (
 port = flag.Int("p", 12305, "port")
)

type server struct {}

func (s server) Ping(ctx context.Context, request *routeguide.PingRequest) (reply *routeguide.PingReply, err error) {
 reply = &routeguide.PingReply{
  Reply:                "pong",
 }

 return
}

func main() {
 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }

 s := grpc.NewServer(grpc.UnaryInterceptor(UnaryServerInterceptor))
 routeguide.RegisterRouteGuideServer(s, &server{})
 s.Serve(lis)
}

启动服务器,用客户端调用后可以看到在服务器和客户端采集到的日志:

代码语言:javascript
复制
// 服务器日志
2021/04/24 17:24:44 ip 127.0.0.1:57258 access_start /routeguide.RouteGuide/Ping in {}
2021/04/24 17:24:44 ip 127.0.0.1:57258 access_end /routeguide.RouteGuide/Ping in {} out {"reply":"pong"} err <nil> duration/ms 0

// 客户端日志
2021/04/24 17:41:11 grpc /routeguide.RouteGuide/Ping in {} out {"reply":"pong"} err <nil> duration/ms 1 

与Web框架的中间件不同的是,Web框架可以给每个 handler 程序应用多个中间件,但是gRPC的客户端和服务器分别可以添加一个单向调用类型的拦截器和流式调用类型的拦截器。不过gRPC社区里Go gRPC Middleware[1] 这个软件包提供了拦截器的interceptor链式的功能,可以将多个拦截器组合成一个拦截器链。

代码语言:javascript
复制
var client routeGuideClient

func init() {
 var err error
 client.cc, err = grpc.Dial(
  "127.0.0.1:12305",
  grpc.WithInsecure(),
  grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(UnaryClientInterceptor)), // 参数里可以添加多个拦截器
 )
 if err != nil {
  panic(err)
 }

}

上面的示例程序为了便于理解做了部分删减,完整可运行的源码可以访问 GitHub 链接[2]获得。

社区里那些实用的拦截器

利用拦截器,可以对gRPC进行扩展,利用社区的力量将gRPC发展壮大,也可以让开发者更灵活地处理gRPC流程中的业务逻辑。下面列出了一些开源社区里发布的实用拦截器。

  1. Go gRPC Middleware[3]:提供了拦截器的interceptor链式的功能,可以将多个拦截器组合成一个拦截器链,当然它还提供了其它的功能,所以以gRPC中间件命名。
  2. grpc-multi-interceptor[4]: 是另一个interceptor链式功能的库,也可以将单向的或者流式的拦截器组合。
  3. grpc_auth[5]: 身份验证拦截器
  4. grpc_ctxtags[6]: 为上下文增加Tag map对象
  5. grpc_zap[7]: 支持zap日志框架
  6. grpc_logrus[8]: 支持logrus日志框架
  7. grpc_prometheus[9]: 支持 prometheus
  8. otgrpc[10]: 支持opentracing/zipkin
  9. grpc_opentracing[11]:支持opentracing/zipkin
  10. grpc_retry[12]: 为客户端增加重试的功能
  11. grpc_validator[13]: 为服务器端增加校验的功能

引用链接

[1]

Go gRPC Middleware: https://github.com/grpc-ecosystem/go-grpc-middleware

[2]

访问 GitHub 链接: https://github.com/kevinyan815/grpcdemo

[3]

Go gRPC Middleware: https://github.com/grpc-ecosystem/go-grpc-middleware

[4]

grpc-multi-interceptor: https://github.com/kazegusuri/grpc-multi-interceptor

[5]

grpc_auth: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth

[6]

grpc_ctxtags: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tags

[7]

grpc_zap: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/zap

[8]

grpc_logrus: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus

[9]

grpc_prometheus: https://github.com/grpc-ecosystem/go-grpc-prometheus

[10]

otgrpc: https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc

[11]

grpc_opentracing: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/tracing/opentracing

[12]

grpc_retry: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/retry

[13]

grpc_validator: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/validator

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 网管叨bi叨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是拦截器
  • 怎么使用拦截器
  • 拦截器应用
  • 社区里那些实用的拦截器
    • 引用链接
    相关产品与服务
    消息队列 TDMQ
    消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档