Edited at

Go 1.8 の HTTP Server Graceful Shutdown を試す

More than 1 year has passed since last update.

Go 1.8 で net/http パッケージに Server.Shutdown メソッドが追加されます。

シグネチャはこちら:

func (srv *Server) Shutdown(ctx context.Context) error


使い方

ざっくりと使い方はこんな感じ。

srv := &http.Server{Addr: ":8080"}

// サーバはブロックするので別の goroutine で実行する
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Print(err)
}
}()

// シグナルを待つ
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM)
<-sigCh

// シグナルを受け取ったらShutdown
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
if err := srv.Shutdown(ctx); err != nil {
log.Print(err)
}


go-server-starter で試してみる

go-server-starter でプロセスを再起動するようにし、ベンチマークをかけてみます。


go get

go get github.com/lestrrat/go-server-starter/cmd/start_server


コード

末尾に掲載しています。


run

サーバを起動します。

> $GOPATH/bin/start_server --port 8080 --pid-file app.pid -- ./go18http &

starting new worker 4043

HTTPリクエストを送ってみます。

> curl http://localhost:8080/

Hello, Go 1.8!


restart

サーバの再起動は HUP です。

> kill -HUP `cat app.pid`

received HUP (num_old_workers=TODO)
spawning a new worker (num_old_workers=TODO)
starting new worker 4051
new worker is now running, sending TERM to old workers:4043
sleep 0 secs
killing old workers
2016/12/13 06:23:01 http: Server closed
old worker 4043 died, status:0


stop

サーバの停止は TERM です。

> kill -TERM `cat app.pid`


ab で負荷をかけてみる

サーバが起動させた状態で、以下のようにシグナル HUP を1秒毎に送り続けて再起動させ続けます。

> while true; do kill -HUP `cat app.pid`; sleep 1; done

別のセッションで ab で負荷をかけてみます。

ab -r -n 100000 -c 1000 http://localhost:8080/


Results

Server Software:

Server Hostname: localhost
Server Port: 8080

Document Path: /
Document Length: 15 bytes

Concurrency Level: 1000
Time taken for tests: 10.674 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 11200000 bytes
HTML transferred: 1500000 bytes
Requests per second: 9368.48 [#/sec] (mean)
Time per request: 106.741 [ms] (mean)
Time per request: 0.107 [ms] (mean, across all concurrent requests)
Transfer rate: 1024.68 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 2.5 1 27
Processing: 100 105 5.4 103 178
Waiting: 0 2 3.4 1 48
Total: 100 106 6.9 105 194

Percentage of the requests served within a certain time (ms)
50% 105
66% 106
75% 107
80% 108
90% 111
95% 114
98% 122
99% 134
100% 194 (longest request)

再起動を繰り返しながらでも、問題なく処理できたようです。


Environment


  • go version go1.8beta2 linux/amd64

  • Amazon EC2 c4.large

  • Linux version 4.4.23-31.54.amzn1.x86_64


コード全体

package main

import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/lestrrat/go-server-starter/listener"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, ")
w.(http.Flusher).Flush()
time.Sleep(time.Millisecond * 100)
fmt.Fprint(w, "Go 1.8!\n")
}

func main() {
listeners, err := listener.ListenAll()
if err != nil && err != listener.ErrNoListeningTarget {
log.Fatal(err)
}

server := &http.Server{Handler: http.HandlerFunc(handler)}

go func(){
if err := server.Serve(listeners[0]); err != nil {
log.Print(err)
}
}()

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM)

<-sigCh
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
if err := server.Shutdown(ctx); err != nil {
log.Print(err)
}
}

License: MIT


当初の記事の公開時点では、コードの書き方に問題があり、Shutdownを待たずにプロセス終了してしまっており、Gracefulになっていませんでした。

shogo82148 さんのコメントのおかげで修正できました。ありがとうございます。