Go 1.8 の HTTP Server Graceful Shutdown を試す

  • 44
    いいね
  • 2
    コメント

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 さんのコメントのおかげで修正できました。ありがとうございます。

この投稿は Go (その3) Advent Calendar 201625日目の記事です。