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

1,732 阅读3分钟

基本使用

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

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

image.png

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

image.png

简单使用:

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

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

viper.Set("age", "181")
viper.WriteConfigAs("config.json")

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

viper.WatchConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
   fmt.Println("config changed",in.name)
})

image.png

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

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

viper 读值要注意的事项

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

if !viper.IsSet("house") {
   fmt.Println("no house key")
}

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

fmt.Println(viper.Get("address.location"))

也可以直接序列化

json 形如:

{
  "age": "181",
  "name": "wuyue",
  "address": {
    "location": "南京"
  }
}

定义结构体

type Config struct {
   Age     string `json:"age"`
   Name    string `json:"name"`
   Address struct {
      Location string `json:"location"`
   } `json:"address"`
}
var config Config
viper.Unmarshal(&config)

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

type Config2 struct {
   Age     int
   Name    string
   Address `mapstructure:"address"`
}

type Address struct {
   Location string
}

优雅关机与重启

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

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

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 类似的进程管理工具 则不需要做优雅重启,只需要稍微关心下优雅关机即可