DEV Community 👩‍💻👨‍💻

Cover image for Interpretation of Open Source Go HTTP Framework | Service Registration Extension Implementation
L2ncE
L2ncE

Posted on

Interpretation of Open Source Go HTTP Framework | Service Registration Extension Implementation

Foreword

During the development iterations of the Hertz framework, I became more and more familiar with Hertz's main library. In the next few articles, I will analyze Hertz's service registration, service discovery and load balancing expansion respectively, and finally use the etcd expansion adapted to Hertz for actual combat. Welcome everyone to pay attention.

Hertz

Hertz is an ultra-large-scale enterprise-level microservice HTTP framework, featuring high ease of use, easy expansion, and low latency etc.

Hertz uses the self-developed high-performance network library Netpoll by default. In some special scenarios, Hertz has certain advantages in QPS and latency compared to go net.

In internal practice, some typical services, such as services with a high proportion of frameworks, gateways and other services, after migrating Hertz, compared to the Gin framework, the resource usage is significantly reduced, CPU usage is reduced by 30%-60% with the size of the traffic.

For more details, see cloudwego/hertz.

Service registration extension

Hertz supports custom registration modules. Users can extend and integrate other registries by themselves. The extension is defined under pkg/app/server/registry.

Extended interface and Info

Service registration interface definition and implementation

The interface definition of service registration is similar to the interface definition of implementing service registration and the implementation of most service registrations. It contains two methods, one for service registration and the other for service deregistration.

// Registry is extension interface of service registry.
type Registry interface {
        Register(info *Info) error
        Deregister(info *Info) error
}
Enter fullscreen mode Exit fullscreen mode

These two methods are implemented in subsequent code in register.go.

// NoopRegistry
type noopRegistry struct{}

func (e noopRegistry) Register(*Info) error {
        return nil
}

func (e noopRegistry) Deregister(*Info) error {
        return nil
}
Enter fullscreen mode Exit fullscreen mode

An empty Registry implementation is also provided in register.go, the default for service registration in the Hertz configuration.

// NoopRegistry is an empty implement of Registry
var NoopRegistry Registry = &noopRegistry{}
Enter fullscreen mode Exit fullscreen mode

It is also the default value for service registration in Hertz configuration.

func NewOptions(opts []Option) *Options {
    options := &Options{
        // ...
        Registry: registry.NoopRegistry,
    }
    options.Apply(opts)
    return options
}
Enter fullscreen mode Exit fullscreen mode

Registration Info

The definition of registration information is provided in registry.go. When using WithRegistry for configuration service registration, the registration information will be initialized and passed to the Register method for subsequent logic execution. These fields are only suggestions, and the exact usage depends on your design.

// Info is used for registry.
// The fields are just suggested, which is used depends on design.
type Info struct {
        // ServiceName will be set in hertz by default
        ServiceName string
        // Addr will be set in hertz by default
        Addr net.Addr
        // Weight will be set in hertz by default
        Weight int
        // extend other infos with Tags.
        Tags map[string]string
}
Enter fullscreen mode Exit fullscreen mode

Timing of service registration

We run initOnRunHooks every time the Spin method is called, and initOnRunHooks contains the timing of our service registration.

func (h *Hertz) initOnRunHooks(errChan chan error) {
        // add register func to runHooks
        opt := h.GetOptions()
        h.OnRun = append(h.OnRun, func(ctx context.Context) error {
                go func() {
                        // delay register 1s
                        time.Sleep(1 * time.Second)
                        if err := opt.Registry.Register(opt.RegistryInfo); err != nil {
                                hlog.SystemLogger().Errorf("Register error=%v", err)
                                // pass err to errChan
                                errChan <- err
                        }
                }()
                return nil
        })
}
Enter fullscreen mode Exit fullscreen mode

When calling initOnRunHooks, we will add the anonymous function registered by the service to runHooks. In this function, we first start a goroutine. Here we will delay one second through time.Sleep. When asynchronous registration, the service will not necessarily start, so there will be a delay of one second to wait for the service to start. After this call Register and pass in the Info from the configuration. If there is an error during registration, pass the error to errChan.

Timing of service deregistration

When receiving a signal that the program exits, Hertz will perform a graceful exit and call the Shutdown method, in which the service will be unregistered. First execute executeOnShutdownHooks concurrently and wait for them until the wait times out or finishes execution. Then it will check whether there is a registered service, and if so, call Deregister to deregister the service.

func (engine *Engine) Shutdown(ctx context.Context) (err error) {
   if atomic.LoadUint32(&engine.status) != statusRunning {
      return errStatusNotRunning
   }
   if !atomic.CompareAndSwapUint32(&engine.status, statusRunning, statusShutdown) {
      return
   }

   ch := make(chan struct{})
   // trigger hooks if any
   go engine.executeOnShutdownHooks(ctx, ch)

   defer func() {
      // ensure that the hook is executed until wait timeout or finish
      select {
      case <-ctx.Done():
         hlog.SystemLogger().Infof("Execute OnShutdownHooks timeout: error=%v", ctx.Err())
         return
      case <-ch:
         hlog.SystemLogger().Info("Execute OnShutdownHooks finish")
         return
      }
   }()

   if opt := engine.options; opt != nil && opt.Registry != nil {
      if err = opt.Registry.Deregister(opt.RegistryInfo); err != nil {
         hlog.SystemLogger().Errorf("Deregister error=%v", err)
         return err
      }
   }

   // call transport shutdown
   if err := engine.transport.Shutdown(ctx); err != ctx.Err() {
      return err
   }

   return
}
Enter fullscreen mode Exit fullscreen mode

Summary

In this article we learned about Hertz's implementation of highly customizable service registry extensions, and analyzed how Hertz integrates it into the core of the framework.

Finally, if the article is helpful to you, please like and share, thank you!

Reference

Top comments (0)

You guessed it

The most popular item in our shop is the stickers. Grab a pack today (with free shipping)!