写点什么

Monibuca v5 实现热重启

作者:不卡科技
  • 2024-04-15
    广东
  • 本文字数:1311 字

    阅读完需:约 4 分钟

优雅关闭

在 v4 中关闭一个流通过改变流的生命周期实现


v4 中流有一个 G(goroutine)专门负责管理流的生命周期,并使用状态自动机来实现状态变更。


但是在退出发布者或者订阅者,仍然遇到一些问题,首先发布者和订阅者各自有自己的 G ,多数用于网络通讯。此外退出分为两种情况,一种是内部原因,比如超时,出错等。另一种是外部原因,比如用户手动关闭,连接断开等。很难优雅的统一处理。


v5 中通过第一性原理思考,移除不必要的 G,不再有管理生命周期的状态机,流和发布者变成同一个概念,实现主动被动退出的统一处理,使得代码进一步简化。

优雅关闭流和订阅者

为了尽量减少锁和 G 的使用,因此选择使用动态 Select 方式,在 Server 层面的一个大 G 中实现,对发布者和订阅者的退出监听。下面是伪代码,为了方便理解


select { case <-server 退出信号: 退出 case <-定时器信号: 定时任务 case <-事件总线信号: 事件处理 case <-发布者 1 退出信号: case <-发布者 2 退出信号: ... case <-订阅者 1 退出信号: case <-订阅者 2 退出信号: ...}
复制代码


为啥优雅呢?因为在一个 G 里面处理,不需要锁,可以方便的修改发布者集合,订阅者集合,以及等待区(订阅时还没有发布者)等很多并发读写的场景。实际上你无法直接写出这个 select,因为发布者和订阅者动态添加和删除的。此时就需要用到 reflect.Select(cases) 了。

优雅关闭 Server

有了优雅关闭发布者和订阅者,那么剩下的就比较简单了,就是要优雅关闭插件。在 v4 中并不支持这种操作。为了能实现动态热更新配置等场景,优雅关闭插件就很重要,因此设计的时候就考虑到了监听和退出监听的逻辑。因此在 sever 退出的时候,需要


  1. 退出所有发布者

  2. 退出所有订阅者

  3. 关闭所有插件的连接监听

  4. 关闭 server 级的 http 和 tcp 监听


所有这些对象都包含了可以用来退出的 context


type Unit struct {    StartTime               time.Time    *slog.Logger            `json:"-" yaml:"-"`    context.Context         `json:"-" yaml:"-"`    context.CancelCauseFunc `json:"-" yaml:"-"`}
func (unit *Unit) Stop(err error) { unit.Info("stop", "reason", err.Error()) unit.CancelCauseFunc(err)}
复制代码


通过传递一个 error 对象,可以用来标记退出的原因。

Server 热重启

本文所说的热重启并非极端意义的连接保持,那种极难实现


有了以上的铺垫,就可以用一个标记为重启的 error 对象来实现 server 的重启:


func (s *Server) Run(ctx context.Context, conf any) (err error) {    for err = s.run(ctx, conf); err == ErrRestart; err = s.run(ctx, conf) {        s.reset()    }    return}
复制代码


在重启时首先会优雅关闭 server,销毁所有资源,然后重新初始化 server 对象,读取配置,初始化插件对象,监听端口。就仿佛进程重启了一样。

实现热重启的好处

进程不再需要退出,对于错误处理更友好,对于 docker 容器来说,进程退出往往就会导致 docker 实例退出。此外重启速度更快,方便快速更新配置。另一个好处是结合多实例,对于单元测试和基准测试更方便,因为单元测试的时候不能退出进程,此时就可以启动多个 server 实例,进行测试,也可以关闭这些实例,测试其他内容。


发布于: 刚刚阅读数: 3
用户头像

不卡科技

关注

令人惊叹的流媒体开源项目 2018-04-23 加入

为中小企业提供优质的流媒体开源框架

评论

发布
暂无评论
Monibuca v5 实现热重启_Go_不卡科技_InfoQ写作社区