grpc教程(四)拓展知识
我们在构建生产级grpc应用程序时,除了定义接口,生成C/S代码和实现业务逻辑外,通常还要提供各种额外的功能,比如拦截器,截止时间,元数据和负载均衡。
01
—
拦截器
我们经常会听说到拦截器,尤其是Spring里AOP讲解中,拦截器是必讲的一个功能。
即我们通常会在远程方法之前或之后,执行一些通用逻辑来满足特定的需求,比如日志,认证,性能度量指标等。这就会使用一种名为拦截器的扩展机制。
gRPC的拦截分类
按功能来分
一元拦截器 UnaryInterceptor
流式拦截器 StreamInterceptor
按端来分
客户端拦截器 ClientInterceptor
服务端拦截器 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后端服务器。
客户端调用负载均衡器请求
如果不使用代理,就要自己在客户端应用程序中实现负载均衡逻辑: