Grpc client 拦截器 CallOption 扩展

本文简单讲述 Grpc client 拦截器 CallOption 扩展.
拦截器应该是最常见的 grpc client 扩展方式, 众多常见服务治理功能都是通过拦截器实现的, 例如: metric, trace, 限流, 熔断.
易用是它很核心的优点, 基本都是配置一次做到业务无感知. 也就是大多数拦截器是全局配置(在同一个 grpc client 连接层面), 但是我们有时候需要对某些接口使用特殊的拦截器配置.
举一个具体的例子: 对于一个 TimeoutInterceptor
我们全局配置是 1s
的超时, 但是该服务某几个接口需要 3s
的响应时间. 常见的处理方式就是被迫把这个连接的超时时间设置成 3.5s
, 也就是取最长接口响应时间作为这个连接的全局超时时间. 这样做的缺点太明显了, 完全就是一种妥协, 假如后面再出现一个 4s
的接口难道再改为 4.5s
吗? 这样大多数情况会使得客户端超时没有意义. 那么我们能否做到单独对这个接口设置 3.5s
配置, 然后全局配置仍然为 1s
呢? 答案是肯定的.
Golang Options Pattern⌗
这里简单聊聊 go 语言的 options 设计模式. 它主要是为了解决 go 语言函数不支持可选参数和参数默认值的缺点.
简单来说函数签名为 func A(a string, opts ...Option)
形式并且提供一些 WithXXX
的函数让你可以指定/修改某些可选参数. 这个模式在大量的 go 项目中使用. 同时还有另一种扩展形式:
grpc client CallOption⌗
当然 grpc client 进行方法调用时也是支持使用 Option 模式传递可选参数. 我们可以查看下函数签名:
因此我们在拦截器里面能够拿到用户在外层调用时传递的 CallOption
参数, 但是可选参数的容器都是私有的(上例中的 option), 所以我们拿不到这些值, 并且我们也没法给它扩展增加属性.
相信聪明的读者已经想到了解决方案, 也就是上面铺垫的 interface
类型的 Option 模式. grpc CallOption 类型为 interface:
但是还是有问题, 因为接口中的方法类型是私有的, 外面没法扩展. 所以 grpc 对外暴露了 EmptyCallOption
这个空的接口实现, 将它内嵌到我们的扩展结构体, 这个结构体就变成了 CallOption
接口. 最终我们可以在拦截器中使用类型断言过滤出我们的扩展类型.
上面的超时拦截器需求就可以这样解决:
总结⌗
每个语言都有自己的特点, 也就演化出了符合自规则的玩法(设计模式). go 语言的 Option 模式是每一个 gopher 都需要掌握的基本技能, 个人觉得在现有框架下还是比较优雅的. 但是 grpc 拦截器参数扩展这种场景不算常见, 因为它的前提是中间件模式, 这种只会在基础框架中出现.