广告位联系
返回顶部
分享到

Go中的应用配置管理介绍

Golang 来源:互联网 作者:佚名 发布时间:2022-09-03 21:17:54 人浏览
摘要

问题 Go语言在编译时不会将配置文件这类第三方文件打包进二进制文件中 它既受当前路径的影响,也会因所填写的不同而改变,并非是绝对可靠的 解决 命令行参数 在Go语言中,可以直

问题

  • Go语言在编译时不会将配置文件这类第三方文件打包进二进制文件中
  • 它既受当前路径的影响,也会因所填写的不同而改变,并非是绝对可靠的

解决

命令行参数

在Go语言中,可以直接通过flag标准库来实现该功能。实现逻辑为,如果存在命令行参数,则优先使用命令行参数,否则使用配置文件中的配置参数。

如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

var (

    port    string

    runMode string

    config  string

)

func init() {  

    // 获取命令行参数

    err = setupFlag()

    if err != nil {

        log.Fatalf("init.setupFlag err: %v", err)

    }

    ......

}

// 获取命令行参数

func setupFlag() error {

    flag.StringVar(&port, "port", "", "启动端口")

    flag.StringVar(&runMode, "mode", "", "启动模式")

    flag.StringVar(&config, "config", "config/", "指定要使用的配置文件路径")

    flag.Parse()

    return nil

}

通过上述代码,我们可以通过标准库flag读取命令行参数,然后根据其默认值判断配置文件是否存在。若存在,则对读取配置的路径进行变更,代码如下:

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

package setting

import "github.com/spf13/viper"

type Setting struct {

    vp *viper.Viper

}

// 初始化配置文件的基础属性

func NewSetting(configs ...string) (*Setting, error) {

    vp := viper.New()

    vp.SetConfigName("config")

    if len(configs) != 0 {

        for _, config := range configs {

            if config != "" {

                vp.AddConfigPath(config)

            }

        }

    } else {

        vp.AddConfigPath("configs/")

    }

    vp.SetConfigType("yaml")

    err := vp.ReadInConfig()

    if err != nil {

        return nil, err

    }

    return &Setting{vp}, nil

}

接下来,对ServerSetting配置项进行覆写。如果存在,则覆盖原有的文件配置,使其优先级更高,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 初始化配置文件

func setupSetting() error {

    setting, err := setting2.NewSetting(strings.Split(config, ",")...)

    if err != nil {

        return err

    }

    ......

    if port != "" {

        global.ServerSetting.HttpPort = port

    }

    if runMode != "" {

        global.ServerSetting.RunMode = runMode

    }

    return nil

}

然后在运行的时候传入参数即可:

1

go run main.go -port=8081 -mode=debug -config=configs/

系统环境变量

可以将配置文件存放在系统自带的全局变量中,如$HOME/conf或/etc/conf中,这样做的好处是不需要重新自定义一个新的系统环境变量。

一般来说,我们会在程序中内置一些系统环境变量的读取,其优先级低于命令行参数,但高于文件配置。

打包进二进制文件

可以将配置文件这种第三方文件打包进二进制文件中,这样就不需要过度关注这些第三方文件了。但这样做是有一定代价的,因此要注意使用的应用场景,即并非所有的项目都能这样操作。

首先安装go-bindata库,安装命令如下:

1

go get -u github.com/go-bindata/go-bindata/...

通过go-bindata库可以将数据文件转换为Go代码。例如,常见的配置文件、资源文件(如Swagger UI)等都可以打包进Go代码中,这样就可以“摆脱”静态资源文件了。接下来在项目根目录下执行生成命令:

1

go-bindata -o configs/config.go -pkg-configs configs/config.yaml

执行这条命令后,会将 configs/config.yaml 文件打包,并通过-o 选项指定的路径输出到configs/config.go文件中,再通过设置的-pkg选项指定生成的packagename为configs,接下来只需执行下述代码,就可以读取对应的文件内容了:

1

b,_:=configs.Asset("configs/config.yaml")

把第三方文件打包进二进制文件后,二进制文件必然增大,而且在常规方法下无法做文件的热更新和监听,必须要重启并且重新打包才能使用最新的内容,因此这种方式是有利有弊的。

配置热更新

开源的fsnotify

既然要做配置热更新,那么首先要知道配置是什么时候修改的,做了哪些事?因此我们需要对所配置的文件进行监听,只有监听到了,才能知道它做了哪些变更。

开源库 fsnotify 是用Go语言编写的跨平台文件系统监听事件库,常用于文件监听,因此我们可以借助该库来实现这个功能。

(1)安装

1

2

go get -u golang.org/x/sys/...

go get -u github.com/fsnotify/fsnotify

fsnotify是基于golang.org/x/sys实现的,并非syscall标准库,因此在安装的同时需要更新其版本,确保版本是最新的。

(2)案例

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

package main

import (

    "gopkg.in/fsnotify.v1"

    "log"

)

func main() {

    watcher, _ := fsnotify.NewWatcher()

    defer watcher.Close()

    done := make(chan bool)

    go func() {

        for {

            select {

            case event, ok := <-watcher.Events:

                if !ok {

                    return

                }

                log.Fatal("event: ", event)

                if event.Op&fsnotify.Write == fsnotify.Write {

                    log.Println("modified file:", event.Name)

                }

            case err, ok := <-watcher.Errors:

                if !ok {

                    return

                }

                log.Fatal("error:", err)

            }

        }

    }()

    path := "configs/config.yaml"

    _ = watcher.Add(path)

    <-done

}

通过监听,我们可以很便捷地知道文件做了哪些变更。进一步来说,我们可以通过对其进行二次封装,在它的上层实现一些变更动作来完成配置文件的热更新。

使用viper开源库实现热更新

viper开源库能够很便捷地实现对文件的监听和热更新。

打开pkg/setting/section.go文件,针对重载应用配置项,新增如下处理方法:

1

2

3

4

5

6

7

8

9

10

11

12

var sections = make(map[string]interface{})

// 解析配置文件

func (s *Setting) ReadSection(k string, v interface{}) error {

    err := s.vp.UnmarshalKey(k, v)

    if err != nil {

        return err

    }

    if _,ok:=sections[k];!ok{

        sections[k] = v

    }

    return nil

}

首先修改ReadSection方法,增加读取section的存储记录,以便在重新加载配置的方法中进行二次处理。接下来新增ReloadAllSection方法,重新读取配置,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// 读取所有配置

func (s *Setting) ReadAllSections() error {

    for k, v := range sections {

        err := s.ReadSection(k, v)

        if err != nil {

            return err

        }

    }

    return nil

}

// 监听配置变化

func (s *Setting) WatchSettingChange()  {

    go func() {

        s.vp.WatchConfig()

        s.vp.OnConfigChange(func(in fsnotify.Event) {

            _ = s.ReloadAllSections()

        })

    }()

}

最后在pkg/setting/setting.go文件中新增文件热更新的监听和变更处理,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// 初始化配置文件的基础属性

func NewSetting(configs ...string) (*Setting, error) {

    vp := viper.New()

    vp.SetConfigName("config")

    for _, config := range configs {

        if config != "" {

            vp.AddConfigPath(config)

        }

    }

    vp.SetConfigType("yaml")

    err := vp.ReadInConfig()

    if err != nil {

        return nil, err

    }

    // 加入热更新

    s := &Setting{vp: vp}

    s.WatchSettingChange()

    return s, nil

}

在上述代码中,首先在NewSetting方法中起一个协程,再在里面通过WatchConfig方法对文件配置进行监听,并在OnConfigChange方法中调用刚刚编写的重载方法ReloadAllSection来处理热更新的文件监听事件回调,这样就可以“悄无声息”地实现一个文件配置热更新了。

OnConfigChange方法的回调方法形参,其实就是fsnotify。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7133036743493681188
相关文章
  • 基于GORM实现CreateOrUpdate的方法
    CreateOrUpdate 是业务开发中很常见的场景,我们支持用户对某个业务实体进行创建/配置。希望实现的 repository 接口要达到以下两个要求: 如果
  • Golang中的内存逃逸的介绍
    什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译
  • Golang自旋锁的介绍
    自旋锁 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。 它是为实现保护共享资源而提出的
  • Go语言读写锁RWMutex的源码

    Go语言读写锁RWMutex的源码
    在前面两篇文章中初见 Go Mutex、Go Mutex 源码详解,我们学习了Go语言中的Mutex,它是一把互斥锁,每次只允许一个goroutine进入临界区,可以保
  • Go项目实现优雅关机与平滑重启功能
    什么是优雅关机? 优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对
  • Go语言操作Excel利器之excelize类库的介绍
    在开发中一些需求需要通过程序操作excel文档,例如导出excel、导入excel、向excel文档中插入图片、表格和图表等信息,使用Excelize就可以方便
  • 利用Go语言快速实现一个极简任务调度系统

    利用Go语言快速实现一个极简任务调度系统
    任务调度(Task Scheduling)是很多软件系统中的重要组成部分,字面上的意思是按照一定要求分配运行一些通常时间较长的脚本或程序。在爬
  • GoLang中的iface 和 eface 的区别介绍

    GoLang中的iface 和 eface 的区别介绍
    GoLang之iface 和 eface 的区别是什么? iface和eface都是 Go 中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface则是不包含任何方
  • Golang接口使用的教程
    go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、
  • go colly 爬虫实现示例介绍
    贡献某CC,go源码爬虫一个,基于colly,效果是根据输入的浏览器cookie及excel必要行列号,从excel中读取公司名称,查询公司法人及电话号码。
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计