konfig
可以进行组合,可观察和性能的配置处理。为较大的分布式系统编写,您可能具有大量的配置源 - 它允许您从多个源中创建带有重新加载钩的配置,从而使构建在高度动态环境中的应用程序易于构建。
这个名字怎么了?
该名称为“ config”。我们在拉拉莫夫(Lalamove)这里有很多国籍,为了庆祝文化多样性,我们大多数开源套餐都会带有一个来自非英语语言的名称,这可能至少有一位员工(?)。
为什么另一个配置软件包?
Golang的大多数配置软件包不是很可扩展,并且很少露出接口。这使得构建可以动态且难以嘲笑的应用程序变得复杂。诸如Vault,ETCD和多个编码格式之类的来源仍然更少。简而言之,我们没有找到一个启动时满足所有要求的软件包。
konfig围绕4个小界面建造:
- 加载程序
- 观察者
- 解析器
- 靠近
konfig功能包括:
- 动态配置加载
- 来自多个来源的可组合加载配置,例如保险库,文件等
- 来自多种格式的Polyglot负载配置。 konfig支持JSON,YAML,TOML,键=值
- 快速,无锁,线程安全读取读数的速度比Viper快10倍
- 可观察的配置 - 热加载机制和管理状态的工具
- 键入读取从配置或绑定结构的键入值
- 指标暴露了Prometheus指标告诉您,如果失败了,将配置重新加载了多少次,以及重新加载需要多长时间
开始
go get github.com/lalamove/ konfig加载并观看JSON格式化的配置文件。
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 . Init ( konfig . DefaultConfig ())
全球商店可直接从包装访问:
konfig . Get ( "foo" ) // calls store.Get("foo")您可以通过调用konfig .New(* konfig .Config)来创建新商店:
s := konfig . New ( konfig . DefaultConfig ())
加载和看商店
在konfig .Store中注册装载机和观察者后,您必须加载并观看商店。
您可以通过调用LoadWatch来做:
if err := konfig . LoadWatch (); err != nil {
log . Fatal ( err )
}
您只能调用Load ,它将加载所有加载程序并返回:
if err := konfig . Load (); err != nil {
log . Fatal ( err )
}
最后,您只能致电Watch ,它将启动所有观察者并返回:
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
}您可以单独或观察者注册配置中的加载程序。
单独注册装载机:
configLoader := konfig . RegisterLoader (
klfile . New (
& klfile. Config {
Files : []klfile. File {
{
Parser : kpjson . Parser ,
Path : "./ konfig .json" ,
},
},
},
),
)
向观察者注册装载器:
要一起注册加载程序和一个观察者,您必须注册一个LoaderWatcher ,该接口同时实现了Loader和Watcher接口。
configLoader := konfig . RegisterLoaderWatcher (
klfile . New (
& klfile. Config {
Files : []klfile. File {
{
Parser : kpjson . Parser ,
Path : "./ konfig .json" ,
},
},
Watch : true ,
},
),
)
您还可以撰写一个加载程序和一个观察者来创建一个LoaderWatcher :
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()调用后运行的功能。它们用于在配置更改上重新加载应用程序的状态。
用一些钩子注册装载机
您可以注册带有钩子的装载机或装载机观察器。
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 。
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 ,以便如果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" )
将类型绑定到商店
如果您希望将配置值未贴到struct或map [string]接口{} {} ,则可以将类型绑定到konfig Store。然后,您可以以线程安全的方式访问该类型的实例(为了安全的动态配置更新安全)。
让我们以JSON配置文件的示例来看看:
{
"addr" : " :8080 " ,
"debug" : true ,
"db" : {
"username" : " foo "
},
"redis" : {
"host" : " 127.0.0.1 "
}
} 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被视为失败。
用法:
// 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可用于严格绑定配置。用法:
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软件包中。在较大的分布式环境中构建应用程序时,这很有用。
带有调试键的配置值集的示例:
debug := konfig . Getter ( "debug" )
debug . Bool () // true
指标
konfig配备了普罗米修斯指标。
暴露了两个指标:
- 带有标签的配置重新加载反向向量
- 配置重新加载持续时间摘要向量带有标签
指标的示例:
# 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 . Init ( & konfig . Config {
Metrics : true ,
Name : "root" ,
})
基准
基准在viper , go-config和konfig上运行。基准是在阅读操作时完成的,并证明konfig在阅读和衬里的3倍上比Viper快3倍:
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
贡献
欢迎捐款。为了做出贡献,将存储库分叉,创建一个分支并向主分支提交拉动请求。
通过命令行克隆项目: