Go中的应用配置管理详解

 更新时间:2022年09月02日 10:01:40   作者:乔克  
这篇文章主要为大家介绍了Go中的应用配置管理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

问题

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

解决

命令行参数

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

如下:

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读取命令行参数,然后根据其默认值判断配置文件是否存在。若存在,则对读取配置的路径进行变更,代码如下:

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配置项进行覆写。如果存在,则覆盖原有的文件配置,使其优先级更高,代码如下:

// 初始化配置文件
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
}

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

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

系统环境变量

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

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

打包进二进制文件

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

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

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

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

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

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

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

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

配置热更新

开源的fsnotify

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

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

(1)安装

go get -u golang.org/x/sys/...
go get -u github.com/fsnotify/fsnotify

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

(2)案例

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文件,针对重载应用配置项,新增如下处理方法:

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方法,重新读取配置,代码如下:

// 读取所有配置
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文件中新增文件热更新的监听和变更处理,代码如下:

// 初始化配置文件的基础属性
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。

以上就是Go中的应用配置管理详解的详细内容,更多关于Go应用配置管理的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Golang的GC三色标记法

    详解Golang的GC三色标记法

    这篇文章主要为大家介绍了Golang的GC三色标记法,文中有详细的实现过程供大家参考,对大家的学习或工作有一定帮助,感兴趣的可以跟着小编一来看看
    2023-05-05
  • golang中单向channel的语法介绍

    golang中单向channel的语法介绍

    通过消息来共享数据是golang的一种设计哲学,channel则是这种哲理的体现。下面这篇文章主要给大家介绍了关于golang中单向channel语法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友们下面来一起看看吧。
    2017-07-07
  • 一文搞懂Golang 值传递还是引用传递

    一文搞懂Golang 值传递还是引用传递

    最多人犯迷糊的就是 slice、map、chan 等类型,都会认为是 “引用传递”,从而认为 Go 语言的 xxx 就是引用传递。正因为它们还引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据,这篇文章主要介绍了Golang 值传递还是引用传递,需要的朋友可以参考下
    2023-01-01
  • win10下go mod配置方式

    win10下go mod配置方式

    这篇文章主要介绍了win10下go mod配置方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • 详解在Go语言单元测试中如何解决文件依赖问题

    详解在Go语言单元测试中如何解决文件依赖问题

    现如今的 Web 应用程序往往采用 RESTful API 接口形式对外提供服务,后端接口直接向前端返回 HTML 文件的情况越来越少,所以在程序中操作文件的场景也变少了,在编写单元测试时,文件就成了被测试代码的外部依赖,本文就来讲解下测试过程中如何解决文件外部依赖问题
    2023-08-08
  • go-zero接入skywalking实现链路追踪的详细教程

    go-zero接入skywalking实现链路追踪的详细教程

    SkyWalking是一个开源的服务追踪系统,它专注于分布式系统的可观测性和服务性能分析,它的主要目标是帮助开发者理解和优化微服务架构中的服务间交互情况,本文介绍了go-zero接入skywalking链路追踪的详细教程,需要的朋友可以参考下
    2024-08-08
  • 深入理解golang的基本类型排序与slice排序

    深入理解golang的基本类型排序与slice排序

    大家都知道排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。下面就来详细介绍golang的基本类型排序与slice排序,有需要的朋友们可以参考借鉴。
    2016-09-09
  • golang修改结构体中的切片值方法

    golang修改结构体中的切片值方法

    这篇文章主要介绍了golang修改结构体中的切片值方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • go语言读取json并下载高清妹子图片

    go语言读取json并下载高清妹子图片

    前面我们介绍了使用python下载高清妹子图,作为程序猿,我们当然不能只会一种语言,今天我们就来使用go语言来读取API来下载妹子图吧,有需要的宅男们可以参考下。
    2015-03-03
  • go实现服务优雅关闭的示例

    go实现服务优雅关闭的示例

    本文主要介绍了go实现服务优雅关闭的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02

最新评论