Golang优雅地结束server

说得这个高端其实主要就是信号量的监听…


环境

  • Go v1.12.7
  • MacOS 10.15
  • iris v11.1.1

POSIX中定义的信号

这里仅列出可能会终结程序的信号量

信号 含义
SIGHUP 1 终端控制进程结束(终端连接断开)
SIGINT 2 用户发送INTR字符(Ctrl+C)触发
SIGKILL 9 无条件结束程序(不能被捕获、阻塞或忽略)
SIGTERM 15 结束程序(可以被捕获、阻塞或忽略)

Golang 监听信号量

就是使用 signal.Notify 来监听,具体怎么用呢,这里摘录一段 iris 中的用法吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (i *interruptListener) notifyAndFire() {
ch := make(chan os.Signal, 1)
signal.Notify(ch,
// kill -SIGINT XXXX or Ctrl+c
os.Interrupt,
syscall.SIGINT, // register that too, it should be ok
// os.Kill is equivalent with the syscall.SIGKILL
os.Kill,
syscall.SIGKILL, // register that too, it should be ok
// kill -SIGTERM XXXX
syscall.SIGTERM,
)
select {
case <-ch:
i.FireNow()
}
}

signal.Notify 传入的第一是一个 chan 类型的变量,后面则是需要监听的信号量。之后则启动一个 select 监听 chan 中传来的事件(也就是相应的信号产生),并及时调用处理函数 FireNow


iris 服务优雅退出

使用默认的信号量监听器

官方提供的相应的示例代码,不但可以使用默认的信号监听器,还可以自定义,以下为官方示例的使用默认信号监听器 ,监听的信号量就是上面的实例代码中列出的:SIGINTSIGKILLSIGTERM

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
package main

import (
stdContext "context"
"time"

"github.com/kataras/iris"
)

// Before continue:
//
// Gracefully Shutdown on control+C/command+C or when kill command sent is ENABLED BY-DEFAULT.
//
// In order to manually manage what to do when app is interrupted,
// We have to disable the default behavior with the option `WithoutInterruptHandler`
// and register a new interrupt handler (globally, across all possible hosts).
func main() {
app := iris.New()

iris.RegisterOnInterrupt(func() {
timeout := 5 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel()
// close all hosts
app.Shutdown(ctx)
})

app.Get("/", func(ctx iris.Context) {
ctx.HTML(" <h1>hi, I just exist in order to see if the server is closed</h1>")
})

// http://localhost:8080
app.Run(iris.Addr(":8080"), iris.WithoutInterruptHandler)
}

当然,你也可以注册一大堆处理函数,如下

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
package main

import (
"context"
"github.com/kataras/iris"
"log"
)

func main() {
app := iris.New()

app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>hi, I just exist in order to see if the server is closed</h1>")
})

iris.RegisterOnInterrupt(func() {
log.Println("on interrupt 1")
})
iris.RegisterOnInterrupt(func() {
log.Println("on interrupt 2")
})
iris.RegisterOnInterrupt(func() {
log.Println("on interrupt 3")
app.Shutdown(context.TODO())
})

app.Run(iris.Addr(":8080"), iris.WithoutInterruptHandler)
}

运行结果如下

1
2
3
4
5
6
Now listening on: http://localhost:8080
Application started. Press CMD+C to shut down.
2019/10/20 18:07:56 on interrupt 1
2019/10/20 18:07:56 on interrupt 2
2019/10/20 18:07:56 on interrupt 3
[ERRO] 2019/10/20 18:07 http: Server closed

自定义信号量监听器

官方代码如下。其实也没么,就是自己手动编写监听信号量的代码而已

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
package main

import (
stdContext "context"
"os"
"os/signal"
"syscall"
"time"

"github.com/kataras/iris"
)

func main() {
app := iris.New()

app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>hi, I just exist in order to see if the server is closed</h1>")
})

go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch,
// kill -SIGINT XXXX or Ctrl+c
os.Interrupt,
syscall.SIGINT, // register that too, it should be ok
// os.Kill is equivalent with the syscall.Kill
os.Kill,
syscall.SIGKILL, // register that too, it should be ok
// kill -SIGTERM XXXX
syscall.SIGTERM,
)
select {
case <-ch:
println("shutdown...")

timeout := 5 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel()
app.Shutdown(ctx)
}
}()

// Start the server and disable the default interrupt handler in order to
// handle it clear and simple by our own, without any issues.
app.Run(iris.Addr(":8080"), iris.WithoutInterruptHandler)
}