Shutdown方法
Go1.8之后有了Shutdown方法,用来优雅的关闭(Graceful Shutdown)服务。
1
|
func (srv *Server) Shutdown(ctx context.Context) error |
这个方法会在调用时进行下述操作:
1.关闭所有open listeners
2.关闭所有idle connections
3.无限期等待connections回归idle状态
4.之后关闭服务
注:3的无限期等待可以用context的超时来解决。
RegisterOnShutdown方法
Go1.9之后有了RegisterOnShutdown方法,用来在优雅的关闭服务的同时处理一些退出任务。
1
|
func (srv *Server) RegisterOnShutdown(f func ()) |
关于RegisterOnShutdown如何生效,我们看代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
type Server struct { ... onShutdown [] func () } func (srv *Server) RegisterOnShutdown(f func ()) { srv.mu.Lock() srv.onShutdown = append (srv.onShutdown, f) srv.mu.Unlock() } func (srv *Server) Shutdown(ctx context.Context) error { ... srv.mu.Lock() ... for _, f := range srv.onShutdown { go f() } srv.mu.Unlock() ... } |
由代码可知,在通过RegisterOnShutdown进行注册之后,这些函数被放入一个函数切片中,并在Shutdown被调用的时候,为每个函数启动了一个goroutine进行处理。
但由于没有进行后续处理,于是这里有个小问题,Shutdown是不等待这些处理函数结束的,就可能比这些处理函数先完成并进而结束程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package main import ( "context" "log" "net/http" "os" "os/signal" "time" ) func main() { var srv http.Server srv.RegisterOnShutdown( func () { time.Sleep( 3 * time.Second) log. Println ( "Exit func" ) }) idleConnsClosed := make ( chan struct {}) go func () { sigint := make ( chan os.Signal, 1 ) signal.Notify(sigint, os.Interrupt) <-sigint // We received an interrupt signal, shut down. if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: log.Printf( "HTTP server Shutdown: %v" , err) } close (idleConnsClosed) }() if err := srv.ListenAndServe(); err != http.ErrServerClosed { // Error starting or closing listener: log.Fatalf( "HTTP server ListenAndServe: %v" , err) } <-idleConnsClosed } |
运行时Ctrl+c之后,服务立刻结束了,并未等待log.Println("Exit func")的输出。
sync.WaitGroup处理退出函数
为了解决这个问题我们使用sync.WaitGroup来处理这些退出函数,于是我们有了下面的代码(解耦并增加了一些日志):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
package main import ( "context" "log" "net/http" "os" "os/signal" "sync" "time" ) func main() { var srv http.Server var wg sync.WaitGroup register := func (f func ()) { wg.Add( 1 ) log. Println ( "Exit func register" ) srv.RegisterOnShutdown( func () { defer wg.Done() f() log. Println ( "Exit func done" ) }) } register( func () { time.Sleep( 3 * time.Second) log. Println ( "Called on Shutdown" ) }) idleConnsClosed := make ( chan struct {}) go func () { sigint := make ( chan os.Signal, 1 ) signal.Notify(sigint, os.Interrupt) <-sigint // We received an interrupt signal, shut down. log. Println ( "Shutdown start" ) if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: log.Printf( "HTTP server Shutdown: %v" , err) } close (idleConnsClosed) }() if err := srv.ListenAndServe(); err != http.ErrServerClosed { // Error starting or closing listener: log.Fatalf( "HTTP server ListenAndServe: %v" , err) } <-idleConnsClosed log. Println ( "Shutdown finish" ) wg.Wait() } |
我们在RegisterOnShutdown时使用了time.Sleep(3 * time.Second)来模拟一个长时间的退出程序,并且在"Shutdown finish"之后进行等待,等待退出程序完成任务。
执行日志如下:
2023/05/12 16:48:53 Exit func register
^C2023/05/12 16:48:54 Shutdown start
2023/05/12 16:48:54 Shutdown finish
2023/05/12 16:48:57 Called on Shutdown
2023/05/12 16:48:57 Exit func done
可以看到这个服务本身很轻,Shutdown start之后立刻就finish并来到wg.Wait(),并等待register的函数完成之后再退出程序。
另外,如果在服务运行的过程中需要启动一些其他的任务,也可以使用
1
2
3
4
5
|
wg.Add( 1 ) go func () { defer wg.Done() ... } |
来保证任务在服务Shutdown之后可以顺利完成。
以上就是go如何优雅关闭Graceful Shutdown服务的详细内容,更多关于go关闭Graceful Shutdown的资料请关注服务器之家其它相关文章!
原文链接:https://segmentfault.com/a/1190000043782350