BOKUのブログ

ジャンルにこだわらず、ノートの感じで書いてます

GO言語標準(net/http)でWebアプリ/安定したシャットダウン(Graceful Shutdown)を実装する

安定したシャットダウン(Graceful Shutdown:グレースフル・シャットダウン)」について学びます。

安定したシャットダウンとは、Webサーバーを停止する際、処理中のリクエストやタスクを安全に完了させてから終了させることで、データの不整合やリソースのリークなど事故を起こさない為に重要です。

Go言語で、これを実装する部分のみを抽出すると以下のような感じになります。

// A
go func() {
	if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("PANIC:サーバー起動失敗: %v", err)
	}
}()

// B
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

// C
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// D
if err := server.Shutdown(ctx); err != nil {
	log.Fatalf("PANIC:サーバーの強制停止エラー: %v", err)
}
   

上記のA,B,C,Dの4つのブロックごとに何をしているかを整理します。 

A:Webサーバーを別スレッド・非同期で起動する

server.ListenAndServe()でWebサーバーを起動しています。でも、普通に実行してしまうと「サーバーが終了するまでその行で処理をブロック(停止)し続ける」ので、B以降の処理が実行されません。それでは具合が悪いので「 go func() 」で囲って、無明関数として別スレッドで非同期実行させて、次の行を実行させるようにしてます。

B:OSからのシグナルがあるまで処理をブロック(停止)する

OSからのシグナルとは「強制終了(Ctrl+Cが押された等)=syscall.SIGINT」や「正常終了(OSからのプロセス終了等=syscall.SIGTERM」の通知のことです。signal.Notifyが、それらを受け取ると「quit」チャネルに通知して、処理を次の行にすすめます。

C:処理の終了を待つタイムアウト時間を設定する

「処理中のリクエストやタスクを安全に完了させてから終了させる」とはいえ、無限ループに陥ってたり、異常に処理時間がかかっているDB処理などの終了を待ち続けるわけにはいきません。なので、これ以上かかる処理は強制的に終わらせる時間を設定(この例では30秒にしてますが)します。

D:シャットダウンします

処理中のリクエストやタスクを安全に完了させる・・もしくは、タイムアウト時間を経過してもまだ続いている処理を強制的に終わらせて・・から終了させます。

 

こうしてみると、A,Bの当たりが肝ですね。ほぼ、定型的な処理で、追加するとすればログにメッセージを書き出すとかくらいかと思います。これで、安定したシャットダウンが実現できるなら、絶対すべきと思います。ではでは。