go init函数_go-micro 源码分析 --- micro package

micro package 是整个go-micro最核心的模块, 除了引用别的包之外, 它由function, micro, options, publisher,service, wrapper这些文件组成。 而这些文件实现了可拔插式功能的微服务架构, 她主要对底层数据实现简单快速开始的封装。 可拔插式指的的可以对默认的实现进行剥离然后自行实现自己想要的, 比如前言里的etcd实际就是插件(只是其他人已经帮我们实现了)。

这个是官网的图。她提供了简单快速的开始, new ===> run。

6b47488c661d47e7e287fe51ab91c577.png

options.go

趁热先看下这个文件, 这个结构体对应的实例即是我们拥有一切配置参数的实例

type Options struct {
    Broker    broker.Broker//Broker: interface只要有那些功能即可
    Cmd       cmd.Cmd//同上
    Client    client.Client//..
    Server    server.Server//..
    Registry  registry.Registry//..
    Transport transport.Transport//..

    BeforeStart []func() error
    BeforeStop  []func() error
    AfterStart  []func() error
    AfterStop   []func() error

    Context context.Context//这要是用来存数据的
}

func newOptions(opts ...Option) Options {
    //新建实例
    opt := Options{
        Broker:    broker.DefaultBroker,
        Cmd:       cmd.DefaultCmd,
        Client:    client.DefaultClient,
        Server:    server.DefaultServer,
        Registry:  registry.DefaultRegistry,
        Transport: transport.DefaultTransport,
        Context:   context.Background(),
    }

    //根据传入的函数修改实例
    for _, o := range opts {
        o(&opt)
    }

    //返回实例
    return opt
}

这个源码里面的其他内容都是返回的option函数的函数。 就是newOptions所需要传入的函数的返回值。

wrap

这里还有WrapHandler``WrapCall,两个函数。 这两个函数比较特殊, 我们以其中一个为例子看下他是干什么。

// WrapCall is a convenience method for wrapping a Client CallFunc
func WrapCall(w ...client.CallWrapper) Option {
    return func(o *Options) {
        //Init是传入函数, 来修改她的实例Client。
        o.Client.Init(client.WrapCall(w...))//Init(...Option) error
    }
}
//这个是client包里面的
// Adds a Wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
    return func(o *Options) {
        //从这里面可以看到, 最终会将CallWrapper函数压成数组
        o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
    }
}

看到这个数组, 如果有开发经验, 你一定想到了中间件, 无论是gin, 还是koa等web框架, 都会维护一个数组来做链式调用.

在client/rpc_client.go 文件的Call函数里面我们能找见这个

for i := len(callOpts.CallWrappers); i > 0; i-- {
    //rcall是真正的调用函数, 但是在调用之前, 我们需要先把wrapper函数跑一遍
    rcall = callOpts.CallWrappers[i-1](rcall)
}

如果熟悉go-kit, 这个东西跟endpoint middleware的实现的方式是一模一样的. 我们可以在里面写一些通用的操作, 比如, 当前服务跑了多少秒, 限流, 熔断什么的

// 记录开始时间
// 调用正真的处理函数
// 处理函数返回后, 打印花费的时间

micro.go

上面图片显示, 启动micro永远都是先NewService, 而这个将会返回一个Service实例。

//只要实现这么多方法的都叫Service实例。 也就是NewService返回的实例会实现这么多方法。
type Service interface {
    Init(...Option)
    Options() Options
    Client() client.Client
    Server() server.Server
    Run() error
    String() string
}

service.go

NewService的方法在这里面定义着

func newService(opts ...Option) Service {
    //newOptions传入opts就知道这个用来修改options的默认参数的
    options := newOptions(opts...)

    options.Client = &clientWrapper{
        options.Client,
        metadata.Metadata{//是个map[string]string类型
            HeaderPrefix + "From-Service": options.Server.Options().Name,
        },
    }

    return &service{
        opts: options,
    }
}

接下来看下Run函数

func (s *service) Run() error {
    if err := s.Start(); err != nil {
        return err
    }

    ch := make(chan os.Signal, 1)
    signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)

    select {
    // wait on kill signal
    case <-ch:
    // wait on context cancel
    case <-s.opts.Context.Done():
    }

    return s.Stop()
}

func (s *service) Start() error {
    for _, fn := range s.opts.BeforeStart {
        if err := fn(); err != nil {
            return err
        }
    }

    if err := s.opts.Server.Start(); err != nil {
        return err
    }

    for _, fn := range s.opts.AfterStart {
        if err := fn(); err != nil {
            return err
        }
    }

    return nil
}

Run函数主要是开启服务, 而这个服务就是service里面Server服务, 对于Grpc来说,他就是grpc服务, 对http来说就是http服务. 但他们都需要在Server的Start函数里面实现. 然后就停住代码, 等待异常退出

wrapper.go

这个文件夹的内容比较简单, 就是将客户端进行一个封装, 将参数变量放在context.Value里面

type clientWrapper struct {
    client.Client
    headers metadata.Metadata
}

func (c *clientWrapper) setHeaders(ctx context.Context) context.Context {
    // copy metadata
    mda, _ := metadata.FromContext(ctx)
    md := metadata.Copy(mda)

    // set headers
    for k, v := range c.headers {
        if _, ok := md[k]; !ok {
            md[k] = v
        }
    }

    //返回context.Value()的Context
    return metadata.NewContext(ctx, md)
}

如果用过GRPC我们都会知道, 除了在pb文件定义请求参数跟返回参数之外, 一般情况下我们还需要定义metadata, metadata里面的数据一般会在整个请求周期都能看见, 比如token什么的都会放在那. 那么如果想使用GRPC将这些东西(headers)发送给其他服务, 那么一定会有一个将metadata(上面自定义的metadata)转化为GRPC(google rpc里面的metadata), 他是在客户端调用的时候需要的, 所以肯定会在client包里面

在client/grpc/grpc.go中, call函数可以看见如下代码

func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
    ...
    if md, ok := metadata.FromContext(ctx); ok {
        for k, v := range md {
            header[k] = v
        }
    }

    // set timeout in nanoseconds
    header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
    // set the content type for the request
    header["x-content-type"] = req.ContentType()

    md := gmetadata.New(header)
    ctx = gmetadata.NewOutgoingContext(ctx, md)
    ...
}

metadata是自定义的metadata包 , gmetadata是grpc的metadata包. 看到NewOutgoingContext用过grpc应该可以看懂.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值