环境变量(配置)
Region、Zone、Cluster、Environment、Color、Discovery、AppID、Host,等之类的环境信息,都是通过在线运行时平台打入到容器或者物理机,供 kit 库读取使用。
静态配置
资源需要初始化的配置信息,比如 http/gRPC server、redis、mysql 等,这类资源在线变更配置的风险非常大,我通常不鼓励 on-the-fly 变更,很可能会导致业务出现不可预期的事故,变更静态配置和发布 bianry app 没有区别,应该走一次迭代发布的流程。
动态配置
应用程序可能需要一些在线的开关,来控制业务的一些简单策略,会频繁的调整和使用,我们把这类是基础类型(int, bool)等配置,用于可以动态变更业务流的收归一起,同时可以考虑结合类似 https://pkg.go.dev/expvar 来结合使用。
全局配置
通常,我们依赖的各类组件、中间件都有大量的默认配置或者指定配置,在各个项目里大量拷贝复制,容易出现意外,所以我们使用全局配置模板来定制化常用的组件,然后再特化的应用里进行局部替换。
Redis client example
// DialTimeout acts like Dial for establishing the// connection to the server, writing a command and reading a reply.func Dial(network, address string) (Conn, error)
“我要自定义超时时间!”
“我要设定 Database!”
“我要控制连接池的策略!”
“我要安全使用 Redis,让我填一下 Password!”
“可以提供一下慢查询请求记录,并且可以设置 slowlog 时间?”
Add Features
// DialTimeout acts like Dial for establishing the// connection to the server, writing a command and reading a reply.func Dial(network, address string) (Conn, error)// DialTimeout acts like Dial but takes timeouts for establishing the// connection to the server, writing a command and reading a reply.func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error)// DialDatabase acts like Dial but takes database for establishing the// connection to the server, writing a command and reading a reply.func DialDatabase(network, address string, database int) (Conn, error)// DialPoolfunc DialPool...
net/http
import ("log""net/http""time")func main() {s := &http.Server{Addr: ":8080",Handler: nil,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}log.Fatal(s.ListenAndServe())}
Configuration struct API
// NewConn new a redis conn.func NewConn(c Config) (cn Conn, err error)// NewConn new a redis conn.func NewConn(c *Config) (cn Conn, err error)// NewConn new a redis conn.func NewConn(c ...*Config) (cn Conn, err error)import ("github.com/go-kratos/kratos/pkg/log")func main() {log.Init(nil) // 这样使用默认配置// config.fix() // 修正默认配置}
“I believe that we, as Go programmers, should work hard to ensure that nil is never a parameter that needs to be passed to any public function.” – Dave Cheney
Functional options
Self-referential functions and the design of options — Rob Pike Functional options for friendly APIs — Dave Cheney
// DialOption specifies an option for dialing a Redis server.type DialOption struct {f func(*dialOptions)}// Dial connects to the Redis server at the given network and// address using the specified options.func Dial(network, address string, options ...DialOption) (Conn, error) {do := dialOptions{dial: net.Dial,}for _, option := range options {option.f(&do)} // ...}
package mainimport ("time""github.com/go-kratos/kratos/pkg/cache/redis")func main() {c, _ := redis.Dial("tcp", "127.0.0.1:3389",redis.DialDatabase(0),redis.DialPassword("hello"),redis.DialReadTimeout(10*time.Second))}
// DialOption specifies an option for dialing a Redis server.type DialOption func(*dialOptions)// Dial connects to the Redis server at the given network and// address using the specified options.func Dial(network, address string, options ...DialOption) (Conn, error) {do := dialOptions{dial: net.Dial,}for _, option := range options {option(&do)}// ...}
type option func(f *Foo) option// Verbosity sets Foo's verbosity level to v.func Verbosity(v int) option {return func(f *Foo) option {prev := f.verbosityf.verbosity = vreturn Verbosity(prev)}}func DoSomethingVerbosely(foo *Foo, verbosity int) {// Could combine the next two lines,// with some loss of readability.prev := foo.Option(pkg.Verbosity(verbosity))defer foo.Option(prev)// ... do some stuff with foo under high verbosity.}
type GreeterClient interface {SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)}type CallOption interface {before(*callInfo) errorafter(*callInfo)}// EmptyCallOption does not alter the Call configuration.type EmptyCallOption struct{}// TimeoutCallOption timeout option.type TimeoutCallOption struct {grpc.EmptyCallOptionTimeout time.Duration}
Hybrid APIs
// Dial connects to the Redis server at the given network and// address using the specified options.func Dial(network, address string, options ...DialOption) (Conn, error)// NewConn new a redis conn.func NewConn(c *Config) (cn Conn, err error)
“JSON/YAML 配置怎么加载,无法映射 DialOption 啊!”
“嗯,不依赖配置的走 options,配置加载走 config ”
Configuration & APIs
package redis// Option configures how we set up the connection.type Option interface {apply(*options)}// 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()...)}
Configuration Best Pratice

代码更改系统功能是一个冗长且复杂的过程,往往还涉及 Review、测试等流程,但更改单个配置选项可能会对功能产生重大影响,通常配置还未经测试。配置的目标:
- 避免复杂
 - 多样的配置
 - 简单化努力
 - 以基础设施 -> 面向用户进行转变
 - 配置的必选项和可选项
 - 配置的防御编程
 - 权限和变更跟踪
 - 配置的版本和应用对齐
 - 安全的配置变更:逐步部署、回滚更改、自动回滚
 
