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