vlambda博客
学习文章列表

grpc教程(四)拓展知识


我们在构建生产级grpc应用程序时,除了定义接口,生成C/S代码和实现业务逻辑外,通常还要提供各种额外的功能,比如拦截器,截止时间,元数据和负载均衡。



01

拦截器


我们经常会听说到拦截器,尤其是Spring里AOP讲解中,拦截器是必讲的一个功能。

即我们通常会在远程方法之前或之后,执行一些通用逻辑来满足特定的需求,比如日志,认证,性能度量指标等。这就会使用一种名为拦截器的扩展机制。


gRPC的拦截分类

  • 按功能来分

  1. 一元拦截器 UnaryInterceptor

  2. 流式拦截器 StreamInterceptor

  • 按端来分

  1. 客户端拦截器 ClientInterceptor

  2. 服务端拦截器 ServerInterceptor


grpc提供了简单的API,用来在客户端和服务端的grpc应用程序中实现拦截器。值得一提的是支持grpc的所有语言并非都支持拦截器功能,grpc每种语言的拦截器实现可能会有所差异。这里着重讲解下golang服务器一元拦截器的实现,有兴趣的话可以自己查一下客户端以及流模式的实现方法。



服务端拦截器示意图

一元拦截器实现方法签名

// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper// of the service method implementation. It is the responsibility of the interceptor to invoke handler// to complete the RPC.type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)


其类型是一个函数,这个函数有,4个入参,两个出参,介绍如下

ctx context.Context 上下文

req interface{} 用户请求的参数

info UnaryServerInfo RPC 方法的所有信息

handler UnaryHandler RPC方法本身

resp interface{} RPC方法执行结果

err error 错误处理


实现grpc一元拦截器的例子:

这里在UnaryServerInterceptor方法中,通过闭包返回了一个设置前置处理的拦截器,并通过handler方法继续一元rpc的正常执行。

func (a *Auth) UnaryServerInterceptor(guest bool) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { // 前置处理逻辑 if ctx, err = a.authMobile(ctx, guest); err != nil { return nil, err } // 调用handler完成一元rpc的正常执行 return handler(ctx, req) }}



02


截止时间


在分布式计算中,截止时间(deadline)和超时时间(timeout)是两个常用模式。


截止时间指的是以请求开始的绝对时间来表示,并且应用于多个服务调用。

超时时间指定客户端应用程序等待rpc的完成时间,通常以持续时长表示并在每个客户端本地进行应用,一般timeout会通过参数设置,而deadline需要在链路中传递。


在golang中,设置grpc的截止时间及取消是通过其中的context包实现的,其中withDeadLine是一个内置函数。golang中context包通常用来向下传递通用的数据,使其在整个下游都能够使用。当客户端应用程序发起调用时,客户端的grpc库会创建grpc的头信息,用来表述客户端应用程序与服务端间的截止时间。

在Java中,其实现直接来源于io.grpc.stub包下的存根实现,可以使用blockingStub.withDeadlineAfter(long java,Util.concurrent)设置grpc的截止时间。




03


元数据metadata


元数据是描述数据的数据。通俗理解是描述对象的共有数据。

在某些场景中,因为预期共享的关于rpc的信息可能与rpc业务上下文并无关联,所以他们不应该作为rpc参数的一部分,在这样的场景中就可以使用grpc元数据。

举个例子,元数据做常见的用途是grpc应用程序间交换安全头信息,比如携带验证信息的authorization,拦截器一般会大量使用这种grpc元数据api。



golang元数据以正常map的形式表述,并可以通过以下形式创建,内容不区分大小写,且相同键的元数据会合并成列表。

metadata.New(map[string]string{"Key1": "val1", "key2": "val2"})

其内置实现方法是:

func New(m map[string]string) MD { md := MD{} for k, val := range m { key := strings.ToLower(k) md[key] = append(md[key], val) } return md}

在客户端/服务端读取元信息,一般通过传入的rpc上下文以metadata.FromIncomingContext(ctx)来实现,他会返回golang的元数据map。

// FromIncomingContext returns the incoming metadata in ctx if it exists. // The returned MD should not be modified. Writing to it may cause races.// Modification should be made to copies of the returned MD.func FromIncomingContext(ctx context.Context) (md MD, ok bool) { md, ok = ctx.Value(mdIncomingKey{}).(MD) return}



04


负载均衡


grpc通常使用两种主要的负载均衡机制:负载均衡代理和客户端负载均衡。

理论上可以使用任意支持http/2的负载均衡器作为grpc应用程序的负载均衡代理,例如nginx代理,envoy代理。

后端服务的拓扑结构对grpc客户端是不透明的,他们只需要知道负载均衡的端点就可以了。客户端发起请求到负载均衡代理端点,代理跟踪每台后台grpc服务器的负载,并将rpc分发给一台可用的grpc后端服务器。

客户端调用负载均衡器请求


如果不使用代理,就要自己在客户端应用程序中实现负载均衡逻辑: