在这个例子中,我将向你展示如何在Go应用程序中优雅地处理HTTP服务器的关闭。这里的重点是在关闭服务器之前等待一定的时间,而不是直接关闭所有的 "实时 "请求/连接。阅读代码注释以了解行和函数的具体作用。有不同的方法来处理优雅的服务器关闭,所以这只是其中的一个例子,主要是基于GolangShutdown文档。我只是保留了必要的代码,以尽可能保持这个例子的相关性。
结构
.
├── cmd
│ └── client
│ └── main.go
├── go.mod
└── internal
├── app
│ ├── client.go
│ ├── handler.go
│ └── server.go
└── user
└── get.go
逻辑
一个 "已知 "的关机信号发生了。例如:Ctrl-C,kill PID,docker stop 或docker down 。如果当时没有 "活 "的请求/连接,则阻止新的请求进入,并关闭服务器,无需等待10 秒。然而,如果有一个或多个 "活 "的请求/连接,则阻止新的请求进入,并允许他们在关闭之前完成他们正在做的事情,10 秒。如果任何 "活着 "的请求/连接超过了10 秒,那么它就会被杀死。
文件
整个代码可以保存在一个文件中,也可以用不同的方式组织,所以由你来改进/重构它。
main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"internal/app"
)
func main() {
// Create the application instance.
application := app.App{
Server: app.Server(app.Handler()),
}
// idleChan channel is dedicated for shutting down all active connections.
// Once actual shutdown occurred by closing this channel, the main goroutine
// is shutdown.
idleChan := make(chan struct{})
go func() {
// signChan channel is used to transmit signal notifications.
signChan := make(chan os.Signal, 1)
// Catch and relay certain signal(s) to signChan channel.
signal.Notify(signChan, os.Interrupt, syscall.SIGTERM)
// Blocking until a signal is sent over signChan channel.
sig := <-signChan
log.Println("shutdown:", sig)
// Create a new context with a timeout duration. It helps allowing
// timeout duration to all active connections in order for them to
// finish their job. Any connections that wont complete within the
// allowed timeout duration gets halted.
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()
if err := application.Stop(ctx); err == context.DeadlineExceeded {
log.Println("shutdown: halted active connections")
}
// Actual shutdown trigger.
close(idleChan)
}()
if err := application.Start(); err == http.ErrServerClosed {
log.Println("shutdown: started")
}
// Blocking until the shutdown to complete then inform the main goroutine.
<-idleChan
log.Println("shutdown: completed")
}
client.go
package app
import (
"context"
"net/http"
)
type App struct {
Server *http.Server
}
func (a App) Start() error {
return a.Server.ListenAndServe()
}
func (a App) Stop(ctx context.Context) error {
return a.Server.Shutdown(ctx)
}
handler.go
package app
import (
"net/http"
"internal/user"
)
func Handler() http.Handler {
handler := http.NewServeMux()
handler.HandleFunc("/client/api/v1/users", user.Get)
return handler
}
server.go
package app
import (
"net/http"
)
func Server(handler http.Handler) *http.Server {
return &http.Server{
Addr: ":8080",
Handler: handler,
}
}
get.go
package user
import (
"fmt"
"net/http"
"time"
)
func Get(w http.ResponseWriter, _ *http.Request) {
time.Sleep(15 * time.Second)
_, _ = w.Write([]byte(fmt.Sprintf("Response: %d", 1)))
}
测试
当有实时连接时:
^C
2020/03/22 20:54:59 shutdown: interrupt
2020/03/22 20:54:59 shutdown: started
2020/03/22 20:55:04 shutdown: halted active connections
2020/03/22 20:55:04 shutdown: completed
当没有任何实时连接时:
^C
2020/03/22 20:54:34 shutdown: interrupt
2020/03/22 20:54:34 shutdown: started
2020/03/22 20:54:39 shutdown: completed