Go中的应用配置管理详解

目录
  • 问题
  • 解决
    • 命令行参数
    • 系统环境变量
    • 打包进二进制文件
  • 配置热更新
    • 开源的fsnotify
    • (1)安装
    • (2)案例
    • 使用viper开源库实现热更新

问题

  • 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应用配置管理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 多个应用共存的Django配置方法

    1.配置环境 安装python3 安装python3-pip 通过pip安装Django **如果需要使用Jinja模板,需要通过pip安装django-jinja与jinja2** 2. 新建Django工程 django-admin startproject rcsiteproject 其目录结构如下图所示: 3.新建app python3 manage.py startapp app1 python3 manage.py startapp app2 4.配置app的urls 在每个app中

  • Go 多环境下配置管理方案(多种方案)

    目录 需求 方案1:配置文件管理 方案2:集中式管理配置 需求 开发过程中开发者经常面对的一个需求就是:一个项目可能会在不同的环境下运行,本地开发环境.测试环境.灰度环境.生产环境.每个环境的参数和配置可能都会不相同,如服务器配置.数据库连接.为避免各环境产生数据混乱,让程序执行时在不同的环境中调用正确的配置,可以这样设计: 命令唤醒程序--->识别环境--->根据环境读取对应配置文件 方案1:配置文件管理 根据环境创建配置文件,多个环境多个配置文件.如开发环境 config-dev.yaml

  • Go微服务项目配置文件的定义和读取示例详解

    目录 前言 场景 定义配置 配置文件 加载配置文件 实现原理 总结 项目地址 前言 我们在写应用时,基本都会用到配置文件,从各种 shell 到 nginx 等,都有自己的配置文件.虽然这没有太多难度,但是配置项一般相对比较繁杂,解析.校验也会比较麻烦.本文就给大家讲讲我们是怎么简化配置文件的定义和解析的. 场景 如果我们要写一个 Restful API 的服务,配置项大概有如下内容: Host,侦听的 IP,如果不填,默认用 0.0.0.0 Port,侦听的端口,必填,只能是数字,大于等于80

  • GO项目配置与使用的方法步骤

    目录 一.Go版本以及GoLand版本 二.环境配置 三.GoLand项目创建 方案一:使用第一种Go(Go modules)创建项目 这里的Go(SDK).GoLand的安装就不多说了,网上自行下载,我们着重讲项目配置,import pkg时能够完美运行 一.Go版本以及GoLand版本 Go版本--------我这里是 Go1.18 GoLand版本--------我这里是2021.2 二.环境配置 在Go开发中,需要配置哪些环境变量 环境量 说明 GOROOT 指定SDK(GO)的安装路径

  • golang配置管理神器Viper使用教程

    目录 Viper 安装 什么是Viper? 为什么选择Viper? 把值存入Viper 建立默认值 读取配置文件 写入配置文件 监控并重新读取配置文件 从io.Reader读取配置 覆盖设置 注册和使用别名 使用环境变量 Env 示例: 使用Flags flag接口 远程Key/Value存储支持 远程Key/Value存储示例-未加密 etcd Consul Firestore 远程Key/Value存储示例-加密 监控etcd中的更改-未加密 从Viper获取值 访问嵌套的键 提取子树 反序

  • Django项目中包含多个应用时对url的配置方法

    一个Django工程中多数情况下会存在多个应用, 如何针对多个应用的url进行配置呢, 有以下两种方案: 1.在Django工程的urls.py中针对每个应用分别配置不同的url路径 2.在工程总体的urls.py中引入每个应用的url配置文件,不同的url路径在各自的配置文件中分别配置 我们首推第二种url的配制方法: 在Django工程的urls.py文件中,引入'blog'应用的url配置文件 在blog自己的urls.py中,引入该views 之后,在工程路径下打开命令窗口,输入pyty

  • Go语言简介和环境配置

    目录 Go语言介绍 1. Go语言的由来 2. Go语言的特点 Go 安装 Linux平台安装Go Windows平台安装Go IDE安装 Linux平台Vim 配置 Vim IDE 常用功能 Windows平台GoLand安装 测试工具安装 Linux平台Curl工具 Windows平台 APIPOST安装 Go语言介绍 1. Go语言的由来 Go语言亦叫Golang语言,是由谷歌Goggle公司推出. 传统的语言比如c++,大家花费太多时间来学习如何使用这门语言,而不是如何更好的表达写作者的

  • Golang配置管理库 Viper的教程详解

    目录 一.Viper 是什么? 二.安装 Viper 三.Viper 有什么作用 四.Viper demo 可供参考 注意 五.总结 一.Viper 是什么? Viper 是应用程序的完整配置的管理工具,用于在应用程序中工作,可以处理所有类型的配置需求和格式. 二.安装 Viper go get github.com/spf13/viper 三.Viper 有什么作用 设置默认值 读取 JSON.TOML.YAML(YML).HCL.envfile 和 Java properties 属性配置文

  • Go中的应用配置管理详解

    目录 问题 解决 命令行参数 系统环境变量 打包进二进制文件 配置热更新 开源的fsnotify (1)安装 (2)案例 使用viper开源库实现热更新 问题 Go语言在编译时不会将配置文件这类第三方文件打包进二进制文件中 它既受当前路径的影响,也会因所填写的不同而改变,并非是绝对可靠的 解决 命令行参数 在Go语言中,可以直接通过flag标准库来实现该功能.实现逻辑为,如果存在命令行参数,则优先使用命令行参数,否则使用配置文件中的配置参数. 如下: var ( port string runM

  • Angularjs中数据绑定的实例详解

    Angularjs中数据绑定的实例详解 这是一个最简单的angularjs的例子,关于数据绑定的,大家可以执行一下,看看效果 <html ng-app> <head> <title>angularjs-include</title> <script type="text/javascript" src="js/angular/angular.min.js"></script> </head

  • 基于angular中的重要指令详解($eval,$parse和$compile)

    在angular的服务中,有一些服务你不得不去了解,因为他可以说是ng的核心,而今天,我要介绍的就是ng的两个核心服务,$parse和$compile.其实这两个服务讲的人已经很多了,但是100个读者就有100个哈姆雷特,我在这里讲讲自己对于他们两个服务的理解. 大家可能会疑问,$eval呢,其实他并不是一个服务,他是scope里面的一个方法,并不能算服务,而且它也基于parse的,所以只能算是$parse的另一种写法而已,我们看一下ng源码中$eval的定义是怎样的就知道了 $eval: fu

  • python中 logging的使用详解

    日志是用来记录程序在运行过程中发生的状况,在程序开发过程中添加日志模块能够帮助我们了解程序运行过程中发生了哪些事件,这些事件也有轻重之分. 根据事件的轻重可分为以下几个级别: DEBUG: 详细信息,通常仅在诊断问题时才受到关注.整数level=10 INFO: 确认程序按预期工作.整数level=20 WARNING:出现了异常,但是不影响正常工作.整数level=30 ERROR:由于某些原因,程序 不能执行某些功能.整数level=40 CRITICAL:严重的错误,导致程序不能运行.整数

  • JavaWeb Servlet中Filter过滤器的详解

    JavaWeb Servlet中Filter过滤器的详解 1.简述 Filter过滤器,对web服务器所有web资源进行过滤,从而实现一些特殊的功能(权限访问控制.过滤敏感词汇.压缩响应信息).Filter能够对Servlet容器的请求和响应进行检查和修改,其本身不能生成请求request和响应response,只提供过滤作用(Servlet被调用之前检查Request对象修改其相关信息,Servlet被调用后检查Response修改其相关信息),Filter对象常驻服务器. 2.Lifecyc

  • C++ 中构造函数的实例详解

    C++ 中构造函数的实例详解 c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初学者有所帮助. 1. 构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类型 Counter() { m_value = 0; } private: // 数据成员 int m_va

  • 基于C++中setiosflags()的用法详解

    cout<<setiosflags(ios::fixed)<<setiosflags(ios::right)<<setprecision(2); setiosflags 是包含在命名空间iomanip 中的C++ 操作符,该操作符的作用是执行由有参数指定区域内的动作:   iso::fixed 是操作符setiosflags 的参数之一,该参数指定的动作是以带小数点的形式表示浮点数,并且在允许的精度范围内尽可能的把数字移向小数点右侧:   iso::right 也是se

  • JSP Spring配置文件中传值的实例详解

    JSP Spring配置文件中传值的实例详解 通过spring提供方法,在配置文件中取传值 调用get方法  targetObject :指定调用的对象       propertyPath:指定调用那个getter方法 例1: public class Test1 { private String name = "nihao"; public String getName() { return name; } } Xml代码 <bean id="t1" cl

  • Angular 中 select指令用法详解

    最近在angular中使用select指令时,出现了很多问题,搞得很郁闷.查看了很多资料后,发现select指令并不简单,决定总结一下. select用法: <select ng-model="" [name=""] [required=""] [ng-required=""] [ng-options=""]> </select> 属性说明: 发现并没有ng-change属性 ng-

  • Angular中的$watch方法详解

    在$apply方法中提到过脏检查,首先apply方法会触发evel方法,当evel方法解析成功后,会去触发digest方法,digest方法会触发watch方法. (1)$watch简介 在digest执行时,如果watch观察的的value与上一次执行时不一样时,就会被触发. AngularJS内部的watch实现了页面随model的及时更新. $watch方法在用的时候主要是手动的监听一个对象,但对象发生变化时触发某个事件. (2)watch方法用法 $watch(watchFn,watch

随机推荐