konfig
可以进行组合,可观察和性能的配置处理。为较大的分布式系统编写,您可能具有大量的配置源 – 它允许您从多个源中创建带有重新加载钩的配置,从而使构建在高度动态环境中的应用程序易于构建。
这个名字怎么了?
该名称为“ config”。我们在拉拉莫夫(Lalamove)这里有很多国籍,为了庆祝文化多样性,我们大多数开源套餐都会带有一个来自非英语语言的名称,这可能至少有一位员工(?)。
为什么另一个配置软件包?
Golang的大多数配置软件包不是很可扩展,并且很少露出接口。这使得构建可以动态且难以嘲笑的应用程序变得复杂。诸如Vault,ETCD和多个编码格式之类的来源仍然更少。简而言之,我们没有找到一个启动时满足所有要求的软件包。
konfig围绕4个小界面建造:
- 加载程序
- 观察者
- 解析器
- 靠近
konfig功能包括:
- 动态配置加载
- 来自多个来源的可组合加载配置,例如保险库,文件等
- 来自多种格式的Polyglot负载配置。 konfig支持JSON,YAML,TOML,键=值
- 快速,无锁,线程安全读取读数的速度比Viper快10倍
- 可观察的配置 – 热加载机制和管理状态的工具
- 键入读取从配置或绑定结构的键入值
- 指标暴露了Prometheus指标告诉您,如果失败了,将配置重新加载了多少次,以及重新加载需要多长时间
开始
go get github.com/lalamove/ konfig
加载并观看JSON格式化的配置文件。
konfig .DefaultConfig())
}
func main() {
// load from json file
konfig .RegisterLoaderWatcher(
klfile.New(&klfile.Config{
Files: configFiles,
Watch: true,
}),
// optionally you can pass config hooks to run when a file is changed
func(c konfig .Store) error {
return nil
},
)
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}
// retrieve value from config file
konfig .Bool(\”debug\”)
}\”>
var configFiles = []klfile. File { { Path : \"./config.json\" , Parser : kpjson . Parser , }, } func init () { konfig . Init ( konfig . DefaultConfig ()) } func main () { // load from json file konfig . RegisterLoaderWatcher ( klfile . New ( & klfile. Config { Files : configFiles , Watch : true , }), // optionally you can pass config hooks to run when a file is changed func ( c konfig . Store ) error { return nil }, ) if err := konfig . LoadWatch (); err != nil { log . Fatal ( err ) } // retrieve value from config file konfig . Bool ( \"debug\" ) }
店铺
该商店是配置软件包的基础。它保存并允许访问密钥存储的值。
创建商店
您可以通过调用konfig .Init(* konfig .Config)来创建一个全局商店:
konfig .DefaultConfig())\”>
konfig . Init ( konfig . DefaultConfig ())
全球商店可直接从包装访问:
konfig . Get ( \"foo\" ) // calls store.Get(\"foo\")
您可以通过调用konfig .New(* konfig .Config)来创建新商店:
konfig .DefaultConfig())\”>
s := konfig . New ( konfig . DefaultConfig ())
加载和看商店
在konfig .Store中注册装载机和观察者后,您必须加载并观看商店。
您可以通过调用LoadWatch来做:
konfig.LoadWatch(); err != nil {
log.Fatal(err)
}\”>
if err := konfig . LoadWatch (); err != nil { log . Fatal ( err ) }
您只能调用Load ,它将加载所有加载程序并返回:
konfig.Load(); err != nil {
log.Fatal(err)
}\”>
if err := konfig . Load (); err != nil { log . Fatal ( err ) }
最后,您只能致电Watch ,它将启动所有观察者并返回:
konfig.Watch(); err != nil {
log.Fatal(err)
}\”>
if err := konfig . Watch (); err != nil { log . Fatal ( err ) }
装载机
加载程序将配置值加载到商店。加载程序是加载程序接口的实现。
type Loader interface { // Name return the name of the load, it is used to create labeled vectors metrics per loader Name () string // StopOnFailure indicates if a failure of the loader should trigger a stop StopOnFailure () bool // Loads the config and add it to the Store Load ( Store ) error // MaxRetry returns the maximum number of times to allow retrying on load failure MaxRetry () int // RetryDelay returns the delay to wait before retrying RetryDelay () time. Duration }
您可以单独或观察者注册配置中的加载程序。
单独注册装载机:
konfig.RegisterLoader(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./ konfig .json\”,
},
},
},
),
)\”>
configLoader := konfig . RegisterLoader ( klfile . New ( & klfile. Config { Files : []klfile. File { { Parser : kpjson . Parser , Path : \"./ konfig .json\" , }, }, }, ), )
向观察者注册装载器:
要一起注册加载程序和一个观察者,您必须注册一个LoaderWatcher ,该接口同时实现了Loader和Watcher接口。
konfig.RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./ konfig .json\”,
},
},
Watch: true,
},
),
)\”>
configLoader := konfig . RegisterLoaderWatcher ( klfile . New ( & klfile. Config { Files : []klfile. File { { Parser : kpjson . Parser , Path : \"./ konfig .json\" , }, }, Watch : true , }, ), )
您还可以撰写一个加载程序和一个观察者来创建一个LoaderWatcher :
konfig.RegisterLoaderWatcher(
// it creates a LoaderWatcher from a loader and a watcher
konfig .NewLoaderWatcher(
someLoader,
someWatcher,
),
)\”>
configLoader := konfig . RegisterLoaderWatcher ( // it creates a LoaderWatcher from a loader and a watcher konfig . NewLoaderWatcher ( someLoader , someWatcher , ), )
内置装载机
konfig已经有以下装载机,他们都有一个内置的观察者:
- 文件加载程序
从可以观看的文件中加载配置。文件可以具有不同的解析器来加载不同的格式。它具有内置文件观察器,该文件观察器会在修改文件时触发配置重新加载(运行挂钩)。
- 金库加载程序
从保险库秘密加载配置。它具有内置的民意调查观察者,该观察器在秘密之前触发配置重新加载(运行钩子),而来自Auth Provider的令牌到期。
- HTTP加载器
从HTTP来源加载配置。来源可以具有不同的解析器来加载不同的格式。它具有内置的民意调查DIFF观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。
- ETCD加载程序
从ETCD键加载配置。密钥可以具有不同的解析器来加载不同的格式。它具有内置的民意调查DIFF观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。
- 领事加载器
从领事KV加载配置。密钥可以具有不同的解析器来加载不同的格式。它构建了Poll Diff观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。
- env加载器
从环境变量加载配置。
- 标志加载程序
从命令行标志加载配置。
- io.Reader加载器
从io.reader加载配置。
解析器
解析器将io.Reader解析为konfig .Store 。某些加载程序使用这些用来将其获取的数据解析到配置存储中。文件加载程序等,ETCD加载程序和HTTP加载程序使用解析器。
配置已经具有以下解析器:
- JSON PARSER
- Toml Parser
- YAML解析器
- KV解析器
- 地图解析器
观察者
观察者在事件上触发加载程序的呼叫。观察者是Watcher接口的实现。
type Watcher interface { // Start starts the watcher, it must not be blocking. Start () error // Done indicate whether the watcher is done or not Done () <- chan struct {} // Watch should block until an event unlocks it Watch () <- chan struct {} // Close closes the watcher, it returns a non nil error if it is already closed // or something prevents it from closing properly. Close () error // Err returns the error attached to the watcher Err () error }
内置观察者
konfig已经有以下观察者:
- 文件观察者
观看文件以进行更改。
- 民意调查员
以给定的速率或启用diff发送事件。它需要一个获取器,并以给定的速率获取数据。如果数据不同,它将发送一个事件。
钩子
挂钩是成功加载器Load()调用后运行的功能。它们用于在配置更改上重新加载应用程序的状态。
用一些钩子注册装载机
您可以注册带有钩子的装载机或装载机观察器。
konfig.RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpyaml.Parser,
Path: \”./ konfig .yaml\”,
},
},
Watch: true,
},
),
func(s konfig .Store) error {
// Here you should reload the state of your app
return nil
},
)\”>
configLoader := konfig . RegisterLoaderWatcher ( klfile . New ( & klfile. Config { Files : []klfile. File { { Parser : kpyaml . Parser , Path : \"./ konfig .yaml\" , }, }, Watch : true , }, ), func ( s konfig . Store ) error { // Here you should reload the state of your app return nil }, )
将钩子添加到现有加载器
您可以注册带有钩子的加载程序或LoaderWatcher 。
konfig.Store) error {
// Here you should reload the state of your app
return nil
},
func(s konfig .Store) error {
// Here you should reload the state of your app
return nil
},
)\”>
configLoader . AddHooks ( func ( s konfig . Store ) error { // Here you should reload the state of your app return nil }, func ( s konfig . Store ) error { // Here you should reload the state of your app return nil }, )
在钥匙上添加钩子
另外,您可以在键上添加钩子。键上的挂钩将匹配前缀,以便在更新带有给定前缀的任何键时运行钩子。即使多个键匹配该钩子,每个加载事件也只能运行一次。
konfig.RegisterKeyHook(
\”db.\”,
func(s konfig .Store) error {
return nil
},
)\”>
konfig . RegisterKeyHook ( \"db.\" , func ( s konfig . Store ) error { return nil }, )
关闭者
可以将关闭器添加到konfig ,以便如果konfig无法加载,它将在注册关闭器上执行Close() 。
type Closer interface { Close () error }
近距离注册
konfig . RegisterCloser ( closer )
配置组
您可以使用配置组命名配置。
konfig.Group(\”db\”).RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpyaml.Parser,
Path: \”./db.yaml\”,
},
},
Watch: true,
},
),
)
// accessing grouped config
dbHost := konfig .Group(\”db\”).MustString(\”credentials.host\”)\”>
konfig . Group ( \"db\" ). RegisterLoaderWatcher ( klfile . New ( & klfile. Config { Files : []klfile. File { { Parser : kpyaml . Parser , Path : \"./db.yaml\" , }, }, Watch : true , }, ), ) // accessing grouped config dbHost := konfig . Group ( \"db\" ). MustString ( \"credentials.host\" )
将类型绑定到商店
如果您希望将配置值未贴到struct或map [string]接口{} {} ,则可以将类型绑定到konfig Store。然后,您可以以线程安全的方式访问该类型的实例(为了安全的动态配置更新安全)。
让我们以JSON配置文件的示例来看看:
{
\"addr\" : \" :8080 \" ,
\"debug\" : true ,
\"db\" : {
\"username\" : \" foo \"
},
\"redis\" : {
\"host\" : \" 127.0.0.1 \"
}
}
konfig:\”db\”`
RedisHost string ` konfig :\”redis.host\”`
}
// we init the root konfig store
konfig .Init( konfig .DefaultConfig())
// we bind the Config struct to the konfig .Store
konfig .Bind(Config{})
// we register our config file
konfig .RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./config.json\”,
},
},
Watch: true,
},
),
)
// we load our config and start watching
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}
// Get our config value
c := konfig .Value().(Config)
fmt.Println(c.Addr) // :8080\”>
type DBConfig struct { Username string } type Config struct { Addr string Debug string DB DBConfig ` konfig :\"db\"` RedisHost string ` konfig :\"redis.host\"` } // we init the root konfig store konfig . Init ( konfig . DefaultConfig ()) // we bind the Config struct to the konfig .Store konfig . Bind ( Config {}) // we register our config file konfig . RegisterLoaderWatcher ( klfile . New ( & klfile. Config { Files : []klfile. File { { Parser : kpjson . Parser , Path : \"./config.json\" , }, }, Watch : true , }, ), ) // we load our config and start watching if err := konfig . LoadWatch (); err != nil { log . Fatal ( err ) } // Get our config value c := konfig . Value ().( Config ) fmt . Println ( c . Addr ) // :8080
请注意,您可以构成配置源。例如,让您的凭据来自保险库,并经常续订,并从文件中加载其余的配置并在文件更改时更新。
重要的是要了解konfig如何将您的配置值列入结构。当加载程序调用konfig .set()时,如果konfig存储具有与之绑定的值,它将尝试将密钥删除到界值。
- 首先,它将在struct中寻找字段标签,如果标签与键完全匹配,它将删除键字段的键。
- 然后,它将在字段名称和键上做一个相同的折叠率,如果它们匹配,它将删除结构字段的键。
- 然后,如果键具有点,它将检查标签或字段名称(小写字母)是钥匙的前缀,如果是的,它将检查字段的类型是否是指针的结构,如果是的,它将使用前缀后的键作为钥匙检查结构。
从配置中读取
除了从绑定的配置值读取外, konfig还提供了几种读取值的方法。
检索配置值的每种方法都有2种口味:
- 在给定键处读取值。如果不存在键,它将返回类型的零值。
- Mustget在给定密钥处读取一个值。如果不存在钥匙,那就恐慌了。
从商店读取值的所有方法:
// Exists checks whether the key k is set in the store. Exists ( k string ) bool // Get gets the value with the key k from the store. If the key is not set, Get returns nil. To check whether a value is really set, use Exists. Get ( k string ) interface {} // MustGet tries to get the value with the key k from the store. If the key k does not exist in the store, MustGet panics. MustGet ( k string ) interface {} // MustString tries to get the value with the key k from the store and casts it to a string. If the key k does not exist in the store, MustString panics. MustString ( k string ) string // String tries to get the value with the key k from the store and casts it to a string. If the key k does not exist it returns the Zero value. String ( k string ) string // MustInt tries to get the value with the key k from the store and casts it to a int. If the key k does not exist in the store, MustInt panics. MustInt ( k string ) int // Int tries to get the value with the key k from the store and casts it to a int. If the key k does not exist it returns the Zero value. Int ( k string ) int // MustFloat tries to get the value with the key k from the store and casts it to a float. If the key k does not exist in the store, MustFloat panics. MustFloat ( k string ) float64 // Float tries to get the value with the key k from the store and casts it to a float. If the key k does not exist it returns the Zero value. Float ( k string ) float64 // MustBool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist in the store, MustBool panics. MustBool ( k string ) bool // Bool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist it returns the Zero value. Bool ( k string ) bool // MustDuration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist in the store, MustDuration panics. MustDuration ( k string ) time . Duration // Duration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist it returns the Zero value. Duration ( k string ) time . Duration // MustTime tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist in the store, MustTime panics. MustTime ( k string ) time . Time // Time tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist it returns the Zero value. Time ( k string ) time . Time // MustStringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist in the store, MustStringSlice panics. MustStringSlice ( k string ) [] string // StringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist it returns the Zero value. StringSlice ( k string ) [] string // MustIntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist in the store, MustIntSlice panics. MustIntSlice ( k string ) [] int // IntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist it returns the Zero value. IntSlice ( k string ) [] int // MustStringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist in the store, MustStringMap panics. MustStringMap ( k string ) map [ string ] interface {} // StringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist it returns the Zero value. StringMap ( k string ) map [ string ] interface {} // MustStringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist in the store, MustStringMapString panics. MustStringMapString ( k string ) map [ string ] string // StringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist it returns the Zero value. StringMapString ( k string ) map [ string ] string
严格的钥匙
您可以通过调用Strict方法来定义konfig .Store上所需的键。当调用严格的方法时, konfig将在商店中设置所需的键,在商店的第一个Load调用期间,将检查键是否存在,如果不存在,则负载将返回非零错误。然后,在加载程序上的每个Load后, konfig将再次检查键是否仍然存在,如果不是, Load被视为失败。
用法:
konfig store
konfig .Init( konfig .DefaultConfig()).Strict(\”debug\”, \”username\”)
// Register our loaders
…
// We load our config and start watching.
// If strict keys are not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}\”>
// We init the root konfig store
konfig .Init( konfig .DefaultConfig()).Strict(\"debug\", \"username\")
// Register our loaders
...
// We load our config and start watching.
// If strict keys are not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}
另外, BindStructStrict可用于严格绑定配置。用法:
konfig:\”-\”` // this key will be non-strict
DB DBConfig ` konfig :\”db\”`
RedisHost string ` konfig :\”redis.host\”`
}
// we init the root konfig store
konfig .Init( konfig .DefaultConfig())
// we bind the Config struct to the konfig .Store
konfig .BindStructStrict(Config{})
// Register our loaders
…
// We load our config and start watching.
// If any strict key is not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}\”>
type DBConfig struct {
Username string
}
type Config struct {
Addr string ` konfig :\"-\"` // this key will be non-strict
DB DBConfig ` konfig :\"db\"`
RedisHost string ` konfig :\"redis.host\"`
}
// we init the root konfig store
konfig .Init( konfig .DefaultConfig())
// we bind the Config struct to the konfig .Store
konfig .BindStructStrict(Config{})
// Register our loaders
...
// We load our config and start watching.
// If any strict key is not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}
Getter
为了轻松构建可以使用动态加载配置的服务,您可以为特定密钥创建Getters。 getter实现了ngetter.GetterTyped从nui软件包中。在较大的分布式环境中构建应用程序时,这很有用。
带有调试键的配置值集的示例:
konfig.Getter(\”debug\”)
debug.Bool() // true\”>
debug := konfig . Getter ( \"debug\" ) debug . Bool () // true
指标
konfig配备了普罗米修斯指标。
暴露了两个指标:
- 带有标签的配置重新加载反向向量
- 配置重新加载持续时间摘要向量带有标签
指标的示例:
konfig_loader_reload Number of config loader reload
# TYPE konfig _loader_reload counter
konfig _loader_reload{loader=\”config-files\”,result=\”failure\”,store=\”root\”} 0.0
konfig _loader_reload{loader=\”config-files\”,result=\”success\”,store=\”root\”} 1.0
# HELP konfig _loader_reload_duration Histogram for the config reload duration
# TYPE konfig _loader_reload_duration summary
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.5\”} 0.001227641
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.9\”} 0.001227641
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.99\”} 0.001227641
konfig _loader_reload_duration_sum{loader=\”config-files\”,store=\”\”} 0.001227641
konfig _loader_reload_duration_count{loader=\”config-files\”,store=\”\”} 1.0\”>
# HELP konfig _loader_reload Number of config loader reload
# TYPE konfig _loader_reload counter
konfig _loader_reload{loader=\"config-files\",result=\"failure\",store=\"root\"} 0.0
konfig _loader_reload{loader=\"config-files\",result=\"success\",store=\"root\"} 1.0
# HELP konfig _loader_reload_duration Histogram for the config reload duration
# TYPE konfig _loader_reload_duration summary
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.5\"} 0.001227641
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.9\"} 0.001227641
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.99\"} 0.001227641
konfig _loader_reload_duration_sum{loader=\"config-files\",store=\"\"} 0.001227641
konfig _loader_reload_duration_count{loader=\"config-files\",store=\"\"} 1.0
要启用指标,您必须在创建配置存储时传递自定义配置:
konfig .Config{
Metrics: true,
Name: \”root\”,
})\”>
konfig . Init ( & konfig . Config { Metrics : true , Name : \"root\" , })
基准
基准在viper , go-config和konfig上运行。基准是在阅读操作时完成的,并证明konfig在阅读和衬里的3倍上比Viper快3倍:
konfig/benchmarks
BenchmarkGet konfig -4 200000000 7.75 ns/op 0 B/op 0 allocs/op
BenchmarkString konfig -4 30000000 49.9 ns/op 0 B/op 0 allocs/op
BenchmarkGetViper-4 20000000 101 ns/op 32 B/op 2 allocs/op
BenchmarkStringViper-4 10000000 152 ns/op 32 B/op 2 allocs/op
BenchmarkGetGoConfig-4 10000000 118 ns/op 40 B/op 3 allocs/op
BenchmarkStringGoConfig-4 10000000 125 ns/op 40 B/op 3 allocs/op
PASS\”>
cd benchmarks && go test -bench . && cd ../
goos: linux
goarch: amd64
pkg: github.com/lalamove/ konfig /benchmarks
BenchmarkGet konfig -4 200000000 7.75 ns/op 0 B/op 0 allocs/op
BenchmarkString konfig -4 30000000 49.9 ns/op 0 B/op 0 allocs/op
BenchmarkGetViper-4 20000000 101 ns/op 32 B/op 2 allocs/op
BenchmarkStringViper-4 10000000 152 ns/op 32 B/op 2 allocs/op
BenchmarkGetGoConfig-4 10000000 118 ns/op 40 B/op 3 allocs/op
BenchmarkStringGoConfig-4 10000000 125 ns/op 40 B/op 3 allocs/op
PASS
贡献
欢迎捐款。为了做出贡献,将存储库分叉,创建一个分支并向主分支提交拉动请求。
