Go Micro Server Source Code Analysis

Posted by Hexxx on Sun, 21 Jul 2019 16:07:51 +0200

Summary

In the Go Micro framework, Server is an encapsulation of services such as Broker, Register, Codec, Transort, as you can see in the figure below.

Look again at the interface defined by Server

  • Init: Initialization
  • Handler: Register rpchandler
  • NewHandler: Encapsulated rpchandler
  • NewSubscriber: Encapsulating Subscriber to Subscribe Method
  • Subscribe://Injection of Subscribe Events
  • Start: Start services, listen on ports, process requests
  • Stop: Stop service, broker and other resources shut down
  • String
type Server interface {
    Options() Options
    Init(...Option) error 
    Handle(Handler) error
    NewHandler(interface{}, ...HandlerOption) Handler
    NewSubscriber(string, interface{}, ...SubscriberOption) Subscriber
    Subscribe(Subscriber) error
    Start() error
    Stop() error
    String() string

Main Method Source Code Take RPC Server as an Example

Init

The initialization method of server. After initializing options, Cmd is initialized by using on function.

func (s *service) Init(opts ...Option) {
    // process options
    for _, o := range opts {
        o(&s.opts)
    }

    s.once.Do(func() {
        // Initialise the command flags, overriding new service
        _ = s.opts.Cmd.Init(
            cmd.Broker(&s.opts.Broker),
            cmd.Registry(&s.opts.Registry),
            cmd.Transport(&s.opts.Transport),
            cmd.Client(&s.opts.Client),
            cmd.Server(&s.opts.Server),
        )
    })
}

Handle

Call router's handler method to register the method in server

func (s *rpcServer) Handle(h Handler) error {
    s.Lock()
    defer s.Unlock()

    if err := s.router.Handle(h); err != nil {
        return err
    }

    s.handlers[h.Name()] = h

    return nil
}

NewSubscriber

Subscribe to a topic and call newSubscriber in subscriber
The newSubscriber function we are in Go Micro Broker Source Code Analysis This article has already been analyzed and will not be expanded here.

func (s *rpcServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
    return newSubscriber(topic, sb, opts...)
}

Subscribe

The Subscribe function accepts a Subscriber interface. You can see the definition of the interface below.
Subscribe functions are saved to subscribers map in rpcServer after they are accepted into the interface
Call the registered Subscriber() Unsubscribe() function in Deregister, Register, Subscribe methods

type Subscriber interface {
    Topic() string
    Subscriber() interface{}
    Endpoints() []*registry.Endpoint
    Options() SubscriberOptions
}
// broker.go 
type Subscriber interface {
    Options() SubscribeOptions
    Topic() string
    Unsubscribe() error
}

func (s *rpcServer) Subscribe(sb Subscriber) error {
    sub, ok := sb.(*subscriber)
    if !ok {
        return fmt.Errorf("invalid subscriber: expected *subscriber")
    }
    if len(sub.handlers) == 0 {
        return fmt.Errorf("invalid subscriber: no handler functions")
    }

    if err := validateSubscriber(sb); err != nil {
        return err
    }

    s.Lock()
    defer s.Unlock()
    _, ok = s.subscribers[sub]
    if ok {
        return fmt.Errorf("subscriber %v already exists", s)
    }
    s.subscribers[sub] = nil
    return nil
}

Start

Start function is the most important function in server. When service run, it calls back the start method of server to officially open the service.
The code is as follows
stay Go Micro Register Source Code Analysis This article has already analyzed the Start function's entry code for service discovery registration, which is not duplicated here. The main things to look at are listening ports, processing sake, and initializing brokers. Overall, it implements listening ports / processing requests / initializing brokers.

func (s *rpcServer) Start() error {
    registerDebugHandler(s)
    config := s.Options()

    // Start listening for transport to process requests received by clients
    ts, err := config.Transport.Listen(config.Address)
    if err != nil {
        return err
    }

    log.Logf("Transport [%s] Listening on %s", config.Transport.String(), ts.Addr())

    // swap address
    s.Lock()
    addr := s.opts.Address
    s.opts.Address = ts.Addr()
    s.Unlock()

    // Connect broker for subscription services
    if err := config.Broker.Connect(); err != nil {
        return err
    }

    bname := config.Broker.String()

    log.Logf("Broker [%s] Connected to %s", bname, config.Broker.Address())

    // Call RegisterCheck to check whether the registration service is available for injection of functions from outside
    if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
        log.Logf("Server %s-%s register check error: %s", config.Name, config.Id, err)
    } else {
        // Registration Service
        if err = s.Register(); err != nil {
            log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
        }
    }

    exit := make(chan bool)
    // Handling the Transport.Listen listerer listened on above
    go func() {
        for {
            // Listening for request processing request code will be explained in more detail in Transport
            err := ts.Accept(s.ServeConn)

            select {
            // check if we're supposed to exit
            case <-exit:
                return
            // check the error and backoff
            default:
                if err != nil {
                    log.Logf("Accept error: %v", err)
                    time.Sleep(time.Second)
                    continue
                }
            }

            // no error just exit
            return
        }
    }()
    // Open the goroutine to check whether the service is available, with a set Register Interval interval
    go func() {
        t := new(time.Ticker)

        // only process if it exists
        if s.opts.RegisterInterval > time.Duration(0) {
            // new ticker
            t = time.NewTicker(s.opts.RegisterInterval)
        }

        // return error chan
        var ch chan error

    Loop:
        for {
            select {
            // register self on interval
            case <-t.C:
                s.RLock()
                registered := s.registered
                s.RUnlock()
                // Call the RegisterCheck method in the Register component to test whether the service is working properly
                if err = s.opts.RegisterCheck(s.opts.Context); err != nil && registered {
                    log.Logf("Server %s-%s register check error: %s, deregister it", config.Name, config.Id, err)
                    // deregister self in case of error
                    if err := s.Deregister(); err != nil {
                        log.Logf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
                    }
                } else {
                    if err := s.Register(); err != nil {
                        log.Logf("Server %s-%s register error: %s", config.Name, config.Id, err)
                    }
                }
            // wait for exit
            case ch = <-s.exit:
                t.Stop()
                close(exit)
                break Loop
            }
        }

        // deregister self
        if err := s.Deregister(); err != nil {
            log.Logf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
        }

        // wait for requests to finish
        if s.wg != nil {
            s.wg.Wait()
        }

        // close transport listener
        ch <- ts.Close()

        // disconnect the broker
        config.Broker.Disconnect()

        // swap back address
        s.Lock()
        s.opts.Address = addr
        s.Unlock()
    }()

    return nil
}

summary

Server is the encapsulation of some underlying methods, such as the opening and closing of services, the registration of nodes and the registration of subscriptions. The same level of encapsulation can be seen in the top figure as well as CLient, but there are services on top of Client and Server, which will be re-analyzed elsewhere.

Topics: Go codec