Go语言 viper与web服务的关机,重启

基本使用

主要就是来读写配置文件的,和方便的库,应该说是目前go语言中 最成熟的一个配置方案了
github.com/spf13/viper

唯一要注意的是 vipe的 设置是有优先级的,另外对于设置的key也是大小写不敏感的

image.png

我们可以简单配置一个文件:

image.png

简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码func main() {
viper.Set("fileDr", "./")
// 读取配置文件
viper.SetConfigName("config") // 配置文件的名称
viper.SetConfigType("json") // 配置文件的扩展名,这里除了json还可以有yaml等格式
// 这个配置可以有多个,主要是告诉viper 去哪个地方找配置文件
// 我们这里就是简单配置下 在当前工作目录下 找配置即可
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
fmt.Println(viper.Get("name"))
}

image.png

除了读配置文件 我们当然也可以写入配置文件

1
2
javascript复制代码viper.Set("age", "181")
viper.WriteConfigAs("config.json")

还可以监控配置文件的变化:

1
2
3
4
go复制代码viper.WatchConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("config changed",in.name)
})

image.png

另外viper还可以支持 远程读取配置信息 比如读取etcd的配置信息

viper一样也支持访问环境变量,有兴趣的可以读一下文档,我这里因为不喜欢操作环境变量。所以就再演示了

viper 读值要注意的事项

没有配置的 会返回0值,所以我们一般用isSet来判断

1
2
3
arduino复制代码if !viper.IsSet("house") {
fmt.Println("no house key")
}

嵌套的key 可以用 . 来做分隔符

1
less复制代码fmt.Println(viper.Get("address.location"))

也可以直接序列化

json 形如:

1
2
3
4
5
6
7
json复制代码{
"age": "181",
"name": "wuyue",
"address": {
"location": "南京"
}
}

定义结构体

1
2
3
4
5
6
7
go复制代码type Config struct {
Age string `json:"age"`
Name string `json:"name"`
Address struct {
Location string `json:"location"`
} `json:"address"`
}
1
2
arduino复制代码var config Config
viper.Unmarshal(&config)

当然这个结构体还可以用mapstructure 来定义

1
2
3
4
5
6
7
8
9
go复制代码type Config2 struct {
Age int
Name string
Address `mapstructure:"address"`
}

type Address struct {
Location string
}

优雅关机与重启

当我们的web服务程序需要更新的时候,通常都是杀掉进程,然后重启即可。但是这样做稍微有点粗鲁,因为假设你杀进程的时候。有请求进来 那么此时进程被杀,这个请求的发起方的体验会很差。

理想种的情况是 我们kill这个web进程的时候 可以 这个web服务可以把手机的活全干完(已经连接上请求,但是程序仍旧在执行 还没有response) 然后再kill自己。

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
go复制代码func main() {
router := gin.Default()
router.GET("/", func(context *gin.Context) {
time.Sleep(5 * time.Second)
context.String(http.StatusOK, "welcome to gin server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
err := srv.ListenAndServe()
if err != nil {
fmt.Println(err)
}
}()
// 等待中断信号来关闭服务器
quit := make(chan os.Signal, 1)
//一般关闭服务器都是直接kill 命令杀进程
//kill -2 一般就是发送的SIGINT信号 我们常用的ctrl c 就是触发系统的sigint信号
//kill -9 一般就是发送的sigkill信号,一般不能捕获 所以也就不用管这个了
//notify 这个操作 是把收到的这些信号 SIGINT SIGTERM 转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 如果chan里面没有数据 那就会阻塞在这
<-quit
log.Println("shutdown server")
// 创建一个5s超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown ")
}
}

实验也很好做,我们在浏览器中访问一个web连接,这个时候gin的代码里我们设置好了要5秒以后才给返回,
然后我们ctrl c kill掉这个进程 你就会发现,程序不是马上就死的,而时等响应之后 才会kill掉自己
浏览器里面也能正常获得响应

image.png

同样的 做到优雅重启也不难

原理就是 当收到信号的时候,我们先fork 一个子进程出来,父进程你就干你还没干完的事情就可以了,
这个时候 新的请求都会到这个 子进程来,等父进程活干完以后 就自己结束就行了

有兴趣的话可以看 github.com/fvbock/endl…

要注意的是如果用了supervisor 类似的进程管理工具 则不需要做优雅重启,只需要稍微关心下优雅关机即可

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%