02使用 Go 读取配置文件

简介

在上次的实践中, 启动了一个基础的 restful api server.

当时的代码中有很多硬编码的属性, 这次就要尝试从配置文件中读取.

使用 viper 读取配置

这里使用 viper 读取配置, 首先安装一下.

go get -u github.com/spf13/viper

创建一个 config 目录, 然后添加 config.go 文件, 在里面定义一个结构 Config, 使用 Name 保存配置路径.

type Config struct {
    Name string
}

然后定义它的两个方法, 一个读取配置, 另一个观察配置的改动.

// 读取配置
func (c *Config) InitConfig() error {
    if c.Name != "" {
        viper.SetConfigFile(c.Name)
    } else {
        viper.AddConfigPath("conf")
        viper.SetConfigName("config")
    }
    viper.SetConfigType("yaml")

    // 从环境变量中读取
    viper.AutomaticEnv()
    viper.SetEnvPrefix("web")
    viper.SetEnvKeyReplacer(strings.NewReplacer("_", "."))

    return viper.ReadInConfig()
}

// 监控配置改动
func (c *Config) WatchConfig(change chan int) {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        log.Printf("配置已经被改变: %s", e.Name)
        change <- 1
    })
}

读取配置时定义了多种方式, 第一个种是没有定义 Config.Name,
c.Name 为空字符串的情况, 这时会从默认路径中寻找配置文件.

另外一种就是直接指定了配置文件的路径, 那是就直接使用这个配置文件.

另外, 激活了从环境变量中读取配置参数, 注意设置了所有环境变量的前缀,
前缀会自动转换为 大写_ 的格式.

另外, 对于多层级的配置参数来说, 会自动将环境变量中的 _ 转换为 ..

举个例子, 当前设置的前缀为 web. 定义一个环境变量名为 WEB_LOG_PATH,
会自动转换为 log.path, 就可以使用 viper.GetString("log.path")
或者这个环境变量对应的值了.

使用 Cobra 创建命令行工具

使用 viper 读取配置之后, 为了更灵活的使用, 势必要使用 CLI 工具,
以便在运行时可以指定参数等.

Cobra 是一个用于创建现代化的 CLI 界面的库, 能提供类似于 git 和 go 工具的能力.

Cobra 的作者就是创建 viper 的作者,
所以这些库都是以 🐍 命名的, viper 是蝰蛇, corba 是眼镜蛇.

corba 擅长于聚合多个命令, 它遵循 命令, 参数, 标志 的理念.

遵从这种理念的模式是 APPNAME VERB NOUN --ADJECTIVE 或者 APPNAME COMMAND ARG --FLAG.

对于我们的 web 项目来说, 目前只有启动这个操作, 所以我们先创建一个主动作.

创建 cmd 目录, 并创建一个名为 root.go 的文件.

var rootCmd = &cobra.Command{
    Use:   "server",
    Short: "server is a simple restful api server",
    Long: `server is a simple restful api server
    use help get more ifo`,
    Run: func(cmd *cobra.Command, args []string) {
        runServer()
    },
}

主要是使用 &cobra.Command 定义一个命令.

里面的参数 Use 定义命令的名字, ShortLong 分别是短长描述,
Run 定义了实际要运行的代码.

定义好主命令之后, 可能需要添加一些操作, 这些都是定义在 init() 函数中的,
同时在里面运行了 cobra.OnInitialize, 这会在每个命令的执行阶段被运行.

// 初始化, 设置 flag 等
func init() {
    cobra.OnInitialize(initConfig)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: ./conf/config.yaml)")
}

// 初始化配置
func initConfig() {
    c := config.Config{
        Name: cfgFile,
    }

    if err := c.InitConfig(); err != nil {
        panic(err)
    }
    log.Printf("载入配置成功")
    c.WatchConfig(configChange)
}

我在这里设置了一个名为 config 的 flag, 即配置文件对应的路径.

最后, 还需要定义一个函数, 用来包装主命令的执行:

// 包装了 rootCmd.Execute()
func Execute() {
    if err := rootCmd.Execute(); err != nil {
        log.Println(err)
        os.Exit(1)
    }
}

如此一来, 主文件 main.go 就非常简单了, 因为我们已经把主要的执行操作,
封装为 runServer(), 并定义在主命令之下了.

func main() {
    cmd.Execute()
}

热重载

前面定义了一个观察 viper 配置改变的函数, 注意到它有个通道参数,
我使用通道作为消息传递机制.

// 监控配置改动
func (c *Config) WatchConfig(change chan int) {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        log.Printf("配置已经被改变: %s", e.Name)
        change <- 1
    })
}

当配置文件被改变之后, 其实它本身会传递一个叫做 fsnotify.Event,
但我没有仔细研究, 而是采用了通道传递消息.

// 定义 rootCmd 命令的执行
func runServer() {
    // 设置运行模式
    gin.SetMode(viper.GetString("runmode"))

    // 初始化空的服务器
    app := gin.New()
    // 保存中间件
    middlewares := []gin.HandlerFunc{}

    // 路由
    router.Load(
        app,
        middlewares...,
    )

    go func() {
        if err := check.PingServer(); err != nil {
            log.Fatal("服务器没有响应", err)
        }
        log.Printf("服务器正常启动")
    }()

    // 服务器的地址和端口
    addr := viper.GetString("addr")
    log.Printf("启动服务器在 http address: %s", addr)

    srv := &http.Server{
        Addr:    addr,
        Handler: app,
    }
    // 启动服务
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // 等待配置改变, 然后重启
    <-configChange
    if err := srv.Shutdown(context.Background()); err != nil {
        log.Fatal("Server Shutdown:", err)
    }
    runServer()
}

前面都是些常规的运行启动, 包括使用一个 goroutine 检查启动的健康状态,
使用另一个 goroutine 启动服务器.

注意最后几行, 我们在等待通道通知配置文件已经发生了改变, 然后开始先关闭服务器,
最后重新运行启动函数.

注意: 这里可能有个 bug, 那就是修改配置文件后, OnConfigChange 会触发两次,
暂时没有什么好的解决方法. 或者可以考虑一下 github issues 上提到的
限流模式.

总结

这个过程主要研究了如何读取配置文件, 同时也使用了命令行相关的库,
便于以后扩展更多的命令.

当前部分的代码

作为版本 0.2.0

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,312评论 0 9
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,381评论 0 5
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,967评论 6 13
  • 写了2次才写完,内容很长,翻译了很久,内容来源于Cobra github介绍。翻译完也更全面的了解了Cobra,功...
    最近不在阅读 28,646评论 0 14
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,848评论 0 5