Interpretation of Open Source Go HTTP Framework | Service Registration Extension Implementation
Tags: webdev, beginners, go, opensource
Published: 2022-11-13 07:51:01

## 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](https://github.com/cloudwego/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](https://github.com/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.

```Go
// Registry is extension interface of service registry.
type Registry interface {
        Register(info *Info) error
        Deregister(info *Info) error
}
```

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

```Go
// NoopRegistry
type noopRegistry struct{}

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

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

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

```Go
// NoopRegistry is an empty implement of Registry
var NoopRegistry Registry = &noopRegistry{}
```

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

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

#### 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.

```Go
// 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
}
```

### Timing of service registration

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

```Go
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
        })
}
```

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.

```Go
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
}
```

## 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

- [cloudwego/hertz](https://github.com/cloudwego/hertz)