go micro server startup analysis

Based on go micro version 2.9.1,

Example code: example/greeter, git commit:3b3de68cded8879ca3dde5d81192f2881619aabd

The core of a microservice server has only three steps

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

First look at micro NewService()

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

micro. The parameters of newservice will return an Option,
These parameters do nothing but return some functions for setting,

This writing method is "function option mode", which can be used for reference https://segmentfault.com/a/11...

First look at what is done in NewService()

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

//Option function in micro Defined in go
//The Options structure is in Options Defined in 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) implements this by setting micro through the functions provided by the micro package options

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

Service is called in NewService Newservice in go (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. Instantiate the service structure, initialize the parameters and assign them to opts

    1. First initialize some Options properties
    2. Then traverse and execute micro from opts The parameter of newservice (actually a function) sets various values
  2. Is options Several wrapper s are added to the client, and four key value pairs are added to ctx, which will be played back to the header when the client initiates the request

    1. wrapper. In fromservice(), ctx add micro from service
    2. wrapper. TraceHandler(trace.DefaultTracer) -> t.Start(ctx, req.Service()+"."+ req. Endpoint()) -> Tracer. Start() (trace / memory / memory. Go), CTX add micro trace ID and micro span ID
    3. server.WrapHandler(wrapper.AuthHandler(authFn)), ctx add micro namespace
  3. Is options Add several wrapper s to the server
options.Server.Init(server.Name(n))

Here are the options Where does the server come from? There is no place to initialize this attribute. In fact, it is in go micro / defaults init() in go

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() defines four variables: server, client, store and trace. It should be noted that the server here is the default grpc, which is an internal variable of the micro package and cannot be accessed directly elsewhere

o.Server. init() of init (server.Name(n)) is server / grpc init() in go initializes grpcserver Some properties of opts [type is server.Options], such as server Name (n) is set to grpcserver opts. Name

grpc. Other parts of configure () will not be examined here for the time being

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...)
}

Let's look at 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. And micro The parameter processing of newservice is the same as that of initialization parameters
  2. s.once.Do() is executed only once

    1. Loading plug-ins
    2. Set cmd name
    3. Initialize the command line parameters and override the properties in the service
    4. Explicitly set the cmd name to the service name

The last step is 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. Register debug handler
  2. profiler, start console, output information
  3. s.start(), see later
  4. Listen for exit Signal and ctx cancel Signal, and execute s.stop() after receiving the Signal
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
}

Start:

  1. Execute s.opts Functions in the beforestart list
  2. Start service s.opts Server. Start (), depending on the service used
  3. Execute s.opts Functions in the afterstart list

sign out:
The exit process is the same as the start process, and execute s.opts BeforeStop,s.opts.Server.Stop(),s.opts.AfterStop

The example of BeforeStart is similar to others

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

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

Default store Defaultstore usage https://github.com/patrickmn/...
In memory / memory Some encapsulation is done in go

Other init() packages referenced in golang will automatically execute init()
logger/default.go initialize logger

micro. See go micro / options for all setting options in newservice() Go, see [micro in action (II): project structure and startup process]
https://medium.com/@dche423/m...

This is the start-up process of go micro microservice. You must understand the function option mode before you can understand go micro.

Tags: Go

Posted by akimm on Tue, 24 May 2022 10:36:22 +0300