go-micro集成链路跟踪的方法和中间件原理( 三 )

注意XXXWrapper实现的接口方法中都去调用了被嵌套Client的对应接口方法,这是能够嵌套执行的关键 。
Wrap Client有了上面的 XXXWrapper,还需要把它注入到程序的执行流程中 。
go-micro在NewService的时候通过调用 micro.WrapClient 设置这些 XXXWrapper:
service := micro.NewService(...micro.WrapClient(tracerClient),)和WrapHandler差不多,WrapClient的参数不是直接传入XXXWrapper的实例,而是一个func,定义如下:
type Wrapper func(Client) Client这个func需要将传入的的Client包装到 XXXWrapper 中,并返回 XXXWrapper 的实例 。这里传入的 tracerClient 就是这样一个func:
return func(c client.Client) client.Client {if ot == nil {ot = opentracing.GlobalTracer()}return &otWrapper{ot, c}}要实现Client的嵌套,可以给定一个初始的Client实例作为第一个此类func的输入,然后前一个func的输出作为后一个func的输入,依次执行,最终形成业务代码中要使用的Client实例,这很像俄罗斯套娃,它有很多层Client 。
那么这个俄罗斯套娃是什么时候创建的呢?
在 micro.NewService -> newService -> newOptions中:
func newOptions(opts ...Option) Options {opt := Options{...Client:client.DefaultClient,...}for _, o := range opts {o(&opt)}return opt}可以看到这里给Client设置了一个初始值,然后遍历这些NewService时传入的Option(WrapClient返回的也是Option),这些Option其实都是func,所以就是遍历执行这些func,执行这些func的时候会传入一些初始默认值,包括Client的初始值 。
那么前一个func的输出怎么作为后一个func的输入的呢?再来看下WrapClient的源码:
func WrapClient(w ...client.Wrapper) Option {return func(o *Options) {for i := len(w); i > 0; i-- {o.Client = w[i-1](o.Client)}}}可以看到Wrap方法从Options中获取到当前的Client实例,把它传给Wrap func,然后新生成的实例又被设置到Options的Client字段中 。
正是这样形成了前文所说的俄罗斯套娃 。
再来看一下客户端调用的执行流程是什么样的?
通过service的Client()方法获取到Client实例,然后通过这个实例的Call()方法执行RPC调用 。
client:=service.Client()client.Call()这个Client实例就是前文描述的套娃实例:
func (s *service) Client() client.Client {return s.opts.Client}前文提到过:XXXWrapper实现的接口方法中调用了被嵌套Client的对应接口方法 。这就是能够嵌套执行的关键 。
这里给一张图,让大家方便理解Wrap Client进行RPC调用的执行流程:

go-micro集成链路跟踪的方法和中间件原理

文章插图
 
客户端Wrap和服务端Wrap的区别一个重要的区别是:对于多次WrapClient,后添加的先被调用;对于多次WrapHandler,先添加的先被调用 。
有一个比较怪异的地方是,WrapClient时如果传递了多个Wrapper实例,WrapClient会把顺序调整过来,这多个实例中前边的先被调用,这个处理和多次WrapClient处理的顺序相反,不是很理解 。
func WrapClient(w ...client.Wrapper) Option {return func(o *Options) {// apply in reversefor i := len(w); i > 0; i-- {o.Client = w[i-1](o.Client)}}}客户端Wrap还提供了更低层级的CallWrapper,它的执行顺序和服务端HandlerWrapper的执行顺序一致,都是先添加的先被调用 。
// wrap the call in reversefor i := len(callOpts.CallWrappers); i > 0; i-- {rcall = callOpts.CallWrappers[i-1](rcall)}还有一个比较大的区别是,服务端的Wrap是调用某个业务Handler之前临时加上的,客户端的Wrap则是在调用Client.Call时就已经创建好 。这样做的原因是什么呢?这个可能是因为在服务端,业务Handler和HandlerWrapper是分别注册的,注册业务Handler时HandlerWrapper可能还不存在,只好采用动态Wrap的方式 。而在客户端,通过Client.Call发起调用时,Client是发起调用的主体,用户有很多获取Client的方式,无法要求用户在每次调用前都临时Wrap 。
Http服务的链路跟踪关于Http或者说是Restful服务的链路跟踪,go-micro的httpClient支持CallWrapper,可以用WrapCall来添加链路跟踪的CallWrapper;但是其httpServer实现的比较简单,把http内部的Handler处理完全交出去了,不能用WrapHandler,只能自己在http的框架中来做这件事,比如go-micro+gin开发的Restful服务可以使用gin的中间件机制来做链路追踪 。


推荐阅读