在参与 Hertz 框架的开发迭代过程中,对 Hertz 的主库也越来越熟悉。接下来的几篇文章我将分别解析 Hertz 的服务注册、服务发现和负载均衡拓展,最后会使用适配于 Hertz 的 etcd 拓展进行实战,欢迎大家关注。
Hertz 是一个超大规模的企业级微服务 HTTP 框架,具有高易用性、易扩展、低时延等特点。
Hertz 默认使用自研的高性能网络库 Netpoll,在一些特殊场景中,相较于 go net,Hertz 在 QPS、时延上均具有一定优势。
在内部实践中,某些典型服务,如框架占比较高的服务、网关等服务,迁移 Hertz 后相比 Gin 框架,资源使用显著减少,CPU 使用率随流量大小降低 30%—60% 。
关于 Hertz 更多的信息可移步至 cloudwego/hertz
Hertz 支持自定义注册模块,使用者可自行扩展集成其他注册中心,该扩展定义在 pkg/app/server/registry
下。
服务注册的接口定义和大部分服务注册的实现类似,含有两个方法,一个为服务注册,另一个为服务取消注册。
// Registry is extension interface of service registry.
type Registry interface {
Register(info *Info) error
Deregister(info *Info) error
}
这两个方法在 register.go
的后续代码中进行了实现。
// NoopRegistry
type noopRegistry struct{}
func (e noopRegistry) Register(*Info) error {
return nil
}
func (e noopRegistry) Deregister(*Info) error {
return nil
}
在 register.go
中还提供了一个空的 Registry 实现,为 Hertz 配置中服务注册的默认值。
// NoopRegistry is an empty implement of Registry
var NoopRegistry Registry = &noopRegistry{}
同时也是 Hertz 配置中服务注册的默认值。
func NewOptions(opts []Option) *Options {
options := &Options{
// ...
Registry: registry.NoopRegistry,
}
options.Apply(opts)
return options
}
在 registry.go
中提供了关于注册信息的定义,在使用 WithRegistry
进行配置服务注册时会初始化注册信息并传入到 Register
方法中进行后续逻辑的执行。这些字段仅为建议,具体的使用取决于自己的设计。
// 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
}
在每次调用 Spin
方法时,我们都会运行 initOnRunHooks
,而在 initOnRunHooks
中包含我们服务注册的时机。
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
})
}
在调用 initOnRunHooks
时,我们会将服务注册的匿名函数添加到 runHooks 中,在此函数中我们先启动一个 goroutine ,这里我们会通过 time.Sleep
延迟一秒,当异步注册时,服务不一定会启动,所以会延迟一秒来等待服务启动。在此之后调用 Register
并将配置中的 Info 传入。若注册时出现错误则将错误传递到 errChan 中。
在接收到程序退出的信号时 Hertz 会进行优雅退出调用 Shutdown
方法,在此方法中会进行服务取消注册。首先并发执行 executeOnShutdownHooks
并等待它们直到等待超时或完成执行。接着就将查看是否有注册的服务,若有则调用 Deregister
进行服务取消注册。
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
}
在这篇文章中我们了解到了 Hertz 可支持高度自定义服务注册拓展的实现,并解析了 Hertz 是如何将其集成在了框架的核心部分。
最后,如果文章对你有帮助,就点赞分享一下吧,谢谢!