go micro server 啓動分析

基於go-micro 2.9.1版本,git

樣例代碼example/greeter,git commit:3b3de68cded8879ca3dde5d81192f2881619aabdgithub

一個微服務server的核心只有3步golang

service := micro.NewService()
service.Init()
service.Run()

先看micro.NewService()web

service := micro.NewService(
    micro.Name("greeter"),
    micro.Version("latest"),
    micro.Metadata(map[string]string{
        "type": "helloworld",
    }),
)

micro.NewService的參數都會返回一個Option,
這些參數沒有作任何事情,只是返回了一些設置用的函數,segmentfault

這種寫法是「函數選項模式」,可參考https://segmentfault.com/a/11...app

先看NewService()裏面作了什麼ide

// Name of the service
func Name(n string) Option {
    return func(o *Options) {
        o.Server.Init(server.Name(n))
    }
}

//Option函數在micro.go中定義
//Options結構體在options.go中定義
type Option func(*Options)

// Options for micro service
type Options struct {
    Auth      auth.Auth
    Broker    broker.Broker
    Cmd       cmd.Cmd
    Config    config.Config
    Client    client.Client
    Server    server.Server
    Store     store.Store
    Registry  registry.Registry
    Router    router.Router
    Runtime   runtime.Runtime
    Transport transport.Transport
    Profile   profile.Profile

    // Before and After funcs
    BeforeStart []func() error
    BeforeStop  []func() error
    AfterStart  []func() error
    AfterStop   []func() error

    // Other options for implementations of the interface
    // can be stored in a context
    Context context.Context

    Signal bool
}

server.Name(n)實現這樣,經過micro包提供的函數設置micro.options函數

// Server name
func Name(n string) Option {
    return func(o *Options) {
        o.Name = n
    }
}

NewService中調用了service.go中的newService(opts...)微服務

// NewService creates and returns a new Service based on the packages within.
func NewService(opts ...Option) Service {
    return newService(opts...)
}
type service struct {
    opts Options
    once sync.Once
}

func newService(opts ...Option) Service {
    service := new(service)
    options := newOptions(opts...)

    // service name
    serviceName := options.Server.Options().Name

    // we pass functions to the wrappers since the values can change during initialisation
    authFn := func() auth.Auth { return options.Server.Options().Auth }
    cacheFn := func() *client.Cache { return options.Client.Options().Cache }

    // wrap client to inject From-Service header on any calls
    options.Client = wrapper.FromService(serviceName, options.Client)
    options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client)
    options.Client = wrapper.CacheClient(cacheFn, options.Client)
    options.Client = wrapper.AuthClient(authFn, options.Client)

    // wrap the server to provide handler stats
    options.Server.Init(
        server.WrapHandler(wrapper.HandlerStats(stats.DefaultStats)),
        server.WrapHandler(wrapper.TraceHandler(trace.DefaultTracer)),
        server.WrapHandler(wrapper.AuthHandler(authFn)),
    )

    // set opts
    service.opts = options
    return service
}

func newOptions(opts ...Option) Options {
    opt := Options{
        Auth:      auth.DefaultAuth,
        Broker:    broker.DefaultBroker,
        Cmd:       cmd.DefaultCmd,
        Config:    config.DefaultConfig,
        Client:    client.DefaultClient,
        Server:    server.DefaultServer,
        Store:     store.DefaultStore,
        Registry:  registry.DefaultRegistry,
        Router:    router.DefaultRouter,
        Runtime:   runtime.DefaultRuntime,
        Transport: transport.DefaultTransport,
        Context:   context.Background(),
        Signal:    true,
    }

    for _, o := range opts {
        o(&opt)
    }

    return opt
}
  1. 實例化service結構體,初始化參數並賦值到optsthis

    1. 先初始化一些Options屬性
    2. 再從opts中遍歷執行micro.NewService的參數(實際就是函數),設置各類值
  2. 爲options.Client增長几個wrapper,ctx中增長了4個鍵值對,在client發起請求的時候回放到header中

    1. wrapper.FromService()中,ctx增長Micro-From-Service
    2. wrapper.TraceHandler(trace.DefaultTracer) -> t.Start(ctx, req.Service()+"."+req.Endpoint()) -> Tracer.start()(trace/memory/memory.go),ctx增長Micro-Trace-Id, Micro-Span-Id
    3. server.WrapHandler(wrapper.AuthHandler(authFn)), ctx增長Micro-Namespace
  3. 爲options.Server增長几個wrapper
options.Server.Init(server.Name(n))

這裏的options.Server是哪裏來的呢,前面沒見有初始化這個屬性的地方,其實在go-micro/defaults.go的init()中

func init() {
    // default client
    client.DefaultClient = gcli.NewClient()
    // default server
    server.DefaultServer = gsrv.NewServer()
    // default store
    store.DefaultStore = memoryStore.NewStore()
    // set default trace
    trace.DefaultTracer = memTrace.NewTracer()
}

init()中定義了4個變量,server,client,store,trace,須要注意的是這裏的server是默認的grpc,是micro包內部變量,在其餘地方沒法直接訪問

o.Server.Init(server.Name(n)) 的 Init() 則是server/grpc.go中的init(),初始化grpcServer.opts[類型是server.Options]的一些屬性,如server.Name(n)設置的是grpcServer.opts.Name

grpc.configure()中的其餘部分這裏暫不細看

func (g *grpcServer) Init(opts ...server.Option) error {
    g.configure(opts...)
    return nil
}

func (g *grpcServer) configure(opts ...server.Option) {
    g.Lock()
    defer g.Unlock()

    // Don't reprocess where there's no config
    if len(opts) == 0 && g.srv != nil {
        return
    }

    for _, o := range opts {
        o(&g.opts)
    }

    maxMsgSize := g.getMaxMsgSize()

    gopts := []grpc.ServerOption{
        grpc.MaxRecvMsgSize(maxMsgSize),
        grpc.MaxSendMsgSize(maxMsgSize),
        grpc.UnknownServiceHandler(g.handler),
    }

    if creds := g.getCredentials(); creds != nil {
        gopts = append(gopts, grpc.Creds(creds))
    }

    if opts := g.getGrpcOptions(); opts != nil {
        gopts = append(gopts, opts...)
    }

    g.rsvc = nil
    g.srv = grpc.NewServer(gopts...)
}

下面再看service.Init()

// Init will parse the command line flags. Any flags set will
    // override the above settings. Options defined here will
    // override anything set on the command line.
    service.Init(
        // Add runtime action
        // We could actually do this above
        micro.Action(func(c *cli.Context) error {
            if c.Bool("run_client") {
                runClient(service)
                os.Exit(0)
            }
            return nil
        }),
    )
// Init initialises options. Additionally it calls cmd.Init
// which parses command line flags. cmd.Init is only called
// on first Init.
func (s *service) Init(opts ...Option) {
    // process options
    for _, o := range opts {
        o(&s.opts)
    }
    s.once.Do(func() {
        // setup the plugins
        for _, p := range strings.Split(os.Getenv("MICRO_PLUGIN"), ",") {
            if len(p) == 0 {
                continue
            }

            // load the plugin
            c, err := plugin.Load(p)
            if err != nil {
                logger.Fatal(err)
            }

            // initialise the plugin
            if err := plugin.Init(c); err != nil {
                logger.Fatal(err)
            }
        }

        // set cmd name
        if len(s.opts.Cmd.App().Name) == 0 {
            s.opts.Cmd.App().Name = s.Server().Options().Name
        }

        // Initialise the command flags, overriding new service
        if err := s.opts.Cmd.Init(
            cmd.Auth(&s.opts.Auth),
            cmd.Broker(&s.opts.Broker),
            cmd.Registry(&s.opts.Registry),
            cmd.Runtime(&s.opts.Runtime),
            cmd.Transport(&s.opts.Transport),
            cmd.Client(&s.opts.Client),
            cmd.Config(&s.opts.Config),
            cmd.Server(&s.opts.Server),
            cmd.Store(&s.opts.Store),
            cmd.Profile(&s.opts.Profile),
        ); err != nil {
            logger.Fatal(err)
        }

        // Explicitly set the table name to the service name
        name := s.opts.Cmd.App().Name
        s.opts.Store.Init(store.Table(name))
    })
}
  1. 和micro.NewService的參數處理同樣,初始化參數
  2. s.once.Do(),只執行一次

    1. 加載插件
    2. 設置cmd名字
    3. 初始化命令行參數,覆蓋service中的屬性
    4. 顯式地將cmd名字設置爲服務名

最後一步service.Run()

func (s *service) Run() error {
    // register the debug handler
    s.opts.Server.Handle(
        s.opts.Server.NewHandler(
            handler.NewHandler(s.opts.Client),
            server.InternalHandler(true),
        ),
    )

    // start the profiler
    if s.opts.Profile != nil {
        // to view mutex contention
        rtime.SetMutexProfileFraction(5)
        // to view blocking profile
        rtime.SetBlockProfileRate(1)

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

    if logger.V(logger.InfoLevel, logger.DefaultLogger) {
        logger.Infof("Starting [service] %s", s.Name())
    }

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

    ch := make(chan os.Signal, 1)
    if s.opts.Signal {
        signal.Notify(ch, signalutil.Shutdown()...)
    }

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

    return s.Stop()
}
  1. 註冊debug handler
  2. 啓動profiler,控制檯輸出啓動信息
  3. s.start(),後面再看
  4. 監聽退出Signal和ctx取消信號,收到信號後執行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
}

func (s *service) Stop() error {
    var gerr error

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

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

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

    return gerr
}

啓動:

  1. 依次執行s.opts.BeforeStart列表中的函數
  2. 啓動服務s.opts.Server.Start(),具體就看用的什麼服務了
  3. 依次執行s.opts.AfterStart列表中的函數

退出:
退出流程與啓動流程一致,依次執行s.opts.BeforeStop,s.opts.Server.Stop(),s.opts.AfterStop

BeforeStart的例子,其餘的相似

func aa() error {
    fmt.Println("beforestart fmt")
    return nil
}

service := micro.NewService(
    micro.BeforeStart(aa),
)

默認的store.DefaultStore使用https://github.com/patrickmn/...
在memory/memory.go中作了一些封裝

其餘init(),在golang中引用的包,會自動執行init()
logger/default.go 初始化logger

micro.NewService()中的全部設置選項見go-micro/options.go,能夠參見【Micro In Action(二):項目結構與啓動過程】
https://medium.com/@dche423/m...

這就是go micro微服務的啓動過程,必定要先了解函數選項模式纔有助於理解go micro。

go micro 分析系列文章
go micro server 啓動分析
go micro client
go micro broker
go micro cmd
go micro config
go micro store
go micro registry
go micro router
go micro runtime
go micro transport
go micro web
go micro registry 插件consul
go micro plugin
go micro jwt 網關鑑權
go micro 鏈路追蹤
go micro 熔斷與限流
go micro wrapper 中間件
go micro metrics 接入Prometheus、Grafana

相關文章
相關標籤/搜索