Monibuca v5 實現熱重啓

優雅關閉

在 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 實例,進行測試,也可以關閉這些實例,測試其他內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章