私は kratos フレームワークを 1 年近く使用してきましたが、最近、プログラムの正常な終了での kratos の具体的な実装について知りました。
ロジックのこの部分は app.go ファイルにあり、メインで app.Run メソッドを見つけて Enter をクリックします。
これには次の部分が含まれます。
-
アプリの構造: アプリケーションの構成オプションと実行時の状態が含まれます。
-
新しい機能: アプリ インスタンスを作成します。
-
実行方法: アプリケーションを起動します。主な手順は次のとおりです。
- ServiceInstance 登録インスタンスを構築する
- サーバーの起動
- インスタンスをサービス検出に登録する
- 停止信号を聞く
-
Stop メソッド: アプリケーションを正常に停止します。主な手順は次のとおりです。
- サービス検出からインスタンスの登録を解除する
- アプリケーションコンテキストをキャンセルします
- サーバーを停止する
-
buildInstance メソッド: サービス検出登録用のインスタンスを構築します。
-
NewContext 関数と FromContext 関数: AppInfo を Context に追加して、後で Context から取得できるようにします。
中心となるロジック フローは次のとおりです。
- アプリインスタンスを作成する
- App.Run()でサーバーを起動し、インスタンスを登録し、信号を監視します。
- 停止信号を受信した後、App.Stop() を呼び出してアプリケーションを停止します。
まずは Run メソッドのソースコードを確認してみましょう
// Run executes all OnStart hooks registered with the application's Lifecycle.
func (a *App) Run() error {
// 构建服务发现注册实例
instance, err := a.buildInstance()
if err != nil {
return err
}
// 保存实例
a.mu.Lock()
a.instance = instance
a.mu.Unlock()
// 创建错误组
eg, ctx := errgroup.WithContext(NewContext(a.ctx, a))
// 等待组,用于等待Server启动完成
wg := sync.WaitGroup{
}
// 启动每个Server
for _, srv := range a.opts.servers {
srv := srv
eg.Go(func() error {
// 等待停止信号
<-ctx.Done()
// 停止Server
stopCtx, cancel := context.WithTimeout(a.opts.ctx, a.opts.stopTimeout)
defer cancel()
return srv.Stop(stopCtx)
})
wg.Add(1)
eg.Go(func() error {
// Server启动完成
wg.Done()
// 启动Server
return srv.Start(NewContext(a.opts.ctx, a))
})
}
// 等待所有Server启动完成
wg.Wait()
// 注册服务实例
if a.opts.registrar != nil {
rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout)
defer rcancel()
if err := a.opts.registrar.Register(rctx, instance); err != nil {
return err
}
}
// 监听停止信号
c := make(chan os.Signal, 1)
signal.Notify(c, a.opts.sigs...)
eg.Go(func() error {
select {
case <-ctx.Done():
return nil
case <-c:
// 收到停止信号,停止应用------------- ⬅️注意此时
return a.Stop()
}
})
// 等待错误组执行完成
if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
}
コアロジックはこちらです⬇️、オペレーティングシステムによって与えられる停止信号を監視するには、signal.Notify を使用します。
// 监听停止信号
c := make(chan os.Signal, 1)
signal.Notify(c, a.opts.sigs...)
eg.Go(func() error {
select {
case <-ctx.Done():
return nil
case <-c:
// 收到停止信号,停止应用
return a.Stop()
}
})
次に、Stop メソッドを呼び出します。Stop のソース コードを見てみましょう。
// Stop gracefully stops the application.
func (a *App) Stop() error {
// 获取服务实例
a.mu.Lock()
instance := a.instance
a.mu.Unlock()
// 从服务发现注销实例
if a.opts.registrar != nil && instance != nil {
ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout)
defer cancel()
if err := a.opts.registrar.Deregister(ctx, instance); err != nil {
return err
}
}
// 取消应用上下文
if a.cancel != nil {
a.cancel()
}
return nil
}
主な手順は次のとおりです。
1. 获取已经保存的服务实例
2. 如果配置了服务发现,则从服务发现中注销该实例
3. 取消应用上下文来通知应用停止
在Run方法中,我们通过context.WithCancel创建的可取消的上下文Context,在这里通过调用cancel函数来取消该上下文,以通知应用停止。
取消上下文会导致在Run方法中启动的协程全部退出,从而优雅停止应用。
所以Stop方法比较简单,关键是利用了Context来控制应用生命周期。
Run メソッドでは、シグナル パッケージの下の Notify メソッドを使用してオペレーティング システムのシャットダウン イベントを監視していることがわかります。これがアクションの中核です。この部分は別の記事で個別に整理しました。
オペレーティング システム イベントを監視することで、完了する必要があるタスクを正常に停止できます。完了する必要があるタスクがある場合は、 wg := sync.WaitGroup{} を使用して、最初にタスクに対して追加操作を実行できます。 task のすべてのタスクが完了し、オペレーティング システムの終了アクションが監視されたら、終了する前に wg.wait() を使用してタスクが完了するのを待つ必要があります。優雅な発進と停止を実現するために。