在部署、运行时能获取到的信息,没必要放到配置文件里。可通过K8S注入到容器或者物理机,供kit 库读取使用。
需要初始化的配置信息,比如 http/gRPC server、redis、mysql 等。
这类资源在线变更配置的风险非常大,不鼓励 on-the-fly 变更,很可能会导致业务出现不可预期的事故。
变更静态配置和发布 bianry app 没有区别,应该走一次迭代发布的流程。
/debug/vars
来暴露这些变量; 针对这些公共变量的 set 或 modify 操作具有原子性;
内部通过并发安全的map存放各种键值对,可用于暴露应用运行指标,例如默认提供的内存状态。
通常,我们依赖的各类组件、中间件都有大量的默认配置或者指定配置,在各个项目里大量拷贝复制,容易出现意外,不好维护。所以我们使用全局配置模板来定制化常用的组件,然后再特化的应用里进行局部替换。
Go 中没有Java里的方法重载,导致配置可选参数时,需要些提供很多不同命、不同参数的方法来进行初始化(例如Redis)。
如何解决:使用可选函数模式,区分必填参数和非必填参数。
参数仅用于一个对象。
type Option func(log *logrus.Logger)
func Level(level logrus.Level) Option {
return func(log *logrus.Logger) {
log.Level = level
}
}
func Output(w io.Writer) Option {
return func(log *logrus.Logger) {
log.Out = w
}
}
...
func NewLogrusLogger(options ...Option) log.Logger {
logger := logrus.New()
// 默认值
logger.Level = logrus.DebugLevel
logger.Out = os.Stdout
logger.Formatter = &logrus.JSONFormatter{}
for _, option := range options {
option(logger)
}
return &LogrusLogger{
log: logger,
}
}
可选参数用于多个对象,或者还需要在其他地方使用可选参数,可将可选参数单独封装为一个对象。
todo gorm db
如果想要用户可以自定义一些配置,可以看看 grpc 的配置定义,主要的思路就是把 option 从函数修改为接口,并提供一些实现类(也可以空实现,交给子类自己重写,例如下面的EmptyCallOption
),自定义配置可以继承实现类,并增加额外的字段。在使用这些可选参数时,通过类型转化得到扩展参数。
type CallOption interface {
before(*callInfo) error
after(*callInfo, *csAttempt)
}
type EmptyCallOption struct{}
func (EmptyCallOption) before(*callInfo) error { return nil }
func (EmptyCallOption) after(*callInfo, *csAttempt) {}
func Header(md *metadata.MD) CallOption {
return HeaderCallOption{HeaderAddr: md}
}
type HeaderCallOption struct {
HeaderAddr *metadata.MD
}
func (o HeaderCallOption) before(c *callInfo) error { return nil }
func (o HeaderCallOption) after(c *callInfo, attempt *csAttempt) {
*o.HeaderAddr, _ = attempt.s.Header()
}
当然可以,kratos就是这么干的,理由如下:
可使用pb里的包装类:https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wrappers.proto
反序列化对象和配置对象定义成一样的结构,赋值时直接拷贝。
不可取,这样强耦合了。并且需要暴露配置对象,这是不能保证的,有的三方包仅提供可选函数配置。
不管是根据配置文件进行配置,还是其他地方需要配置,都使用可选函数模式,不要提供多种初始化方式,这样基础库才精简。
使用时,额外写个反序列化对象转option数组的函数,但不能封装到kit库,因为这个反序列对象是使用者通过pb定义的,大家都不同。
// Options apply config to options.
func (c *Config) Options() []redis.Options {
return []redis.Options{
redis.DialDatabase(c.Database),
redis.DialPassword(c.Password),
redis.DialReadTimeout(c.ReadTimeout),
}
}
func main() {
// instead use load yaml file.
c := &Config{
Network: "tcp",
Addr: "127.0.0.1:3389",
Database: 1,
Password: "Hello",
ReadTimeout: 1 * time.Second,
}
r, _ := redis.Dial(c.Network, c.Addr, c.Options()...)
}
个人认为反序列化对象转option数组的函数意义不大,还是直接给可选函数赋值简单。
另外毛老师说kratos讲配置定义的pb文件生成go代码时,直接额外生成转option的函数,这是不可行的,因为option是基础库里定义的,kratos现在也并没有这么做。
代码更改系统功能是一个冗长且复杂的过程,往往还涉及Review、测试等流程,但更改单个配置选项可能会对功能产生重大影响,通常配置还未经测试。
修改配置其实是一件比较危险的事情,很多时候我们缺乏足够的敬畏,因为现在在线的配置中心越来方便,所以修改的成本越来越低,大家就越来越随意,所以我们需要对配置的修改慎重一些。
配置的目标:
GO 编程模式:FUNCTIONAL OPTIONS——陈皓
Post Views: 5