• 环境变量(配置)

    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

  1. // DialTimeout acts like Dial for establishing the
  2. // connection to the server, writing a command and reading a reply.
  3. func Dial(network, address string) (Conn, error)

“我要自定义超时时间!”

“我要设定 Database!”

“我要控制连接池的策略!”

“我要安全使用 Redis,让我填一下 Password!”

“可以提供一下慢查询请求记录,并且可以设置 slowlog 时间?”

Add Features

  1. // DialTimeout acts like Dial for establishing the
  2. // connection to the server, writing a command and reading a reply.
  3. func Dial(network, address string) (Conn, error)
  4. // DialTimeout acts like Dial but takes timeouts for establishing the
  5. // connection to the server, writing a command and reading a reply.
  6. func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error)
  7. // DialDatabase acts like Dial but takes database for establishing the
  8. // connection to the server, writing a command and reading a reply.
  9. func DialDatabase(network, address string, database int) (Conn, error)
  10. // DialPool
  11. func DialPool...

net/http

  1. import (
  2. "log"
  3. "net/http"
  4. "time"
  5. )
  6. func main() {
  7. s := &http.Server{
  8. Addr: ":8080",
  9. Handler: nil,
  10. ReadTimeout: 10 * time.Second,
  11. WriteTimeout: 10 * time.Second,
  12. MaxHeaderBytes: 1 << 20,
  13. }
  14. log.Fatal(s.ListenAndServe())
  15. }

image.png

Configuration struct API

  1. // NewConn new a redis conn.
  2. func NewConn(c Config) (cn Conn, err error)
  3. // NewConn new a redis conn.
  4. func NewConn(c *Config) (cn Conn, err error)
  5. // NewConn new a redis conn.
  6. func NewConn(c ...*Config) (cn Conn, err error)
  7. import (
  8. "github.com/go-kratos/kratos/pkg/log"
  9. )
  10. func main() {
  11. log.Init(nil) // 这样使用默认配置
  12. // config.fix() // 修正默认配置
  13. }

“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

  1. // DialOption specifies an option for dialing a Redis server.
  2. type DialOption struct {
  3. f func(*dialOptions)
  4. }
  5. // Dial connects to the Redis server at the given network and
  6. // address using the specified options.
  7. func Dial(network, address string, options ...DialOption) (Conn, error) {
  8. do := dialOptions{
  9. dial: net.Dial,
  10. }
  11. for _, option := range options {
  12. option.f(&do)
  13. } // ...
  14. }
  1. package main
  2. import (
  3. "time"
  4. "github.com/go-kratos/kratos/pkg/cache/redis"
  5. )
  6. func main() {
  7. c, _ := redis.Dial("tcp", "127.0.0.1:3389",
  8. redis.DialDatabase(0),
  9. redis.DialPassword("hello"),
  10. redis.DialReadTimeout(10*time.Second))
  11. }
  1. // DialOption specifies an option for dialing a Redis server.
  2. type DialOption func(*dialOptions)
  3. // Dial connects to the Redis server at the given network and
  4. // address using the specified options.
  5. func Dial(network, address string, options ...DialOption) (Conn, error) {
  6. do := dialOptions{
  7. dial: net.Dial,
  8. }
  9. for _, option := range options {
  10. option(&do)
  11. }
  12. // ...
  13. }
  1. type option func(f *Foo) option
  2. // Verbosity sets Foo's verbosity level to v.
  3. func Verbosity(v int) option {
  4. return func(f *Foo) option {
  5. prev := f.verbosity
  6. f.verbosity = v
  7. return Verbosity(prev)
  8. }
  9. }
  10. func DoSomethingVerbosely(foo *Foo, verbosity int) {
  11. // Could combine the next two lines,
  12. // with some loss of readability.
  13. prev := foo.Option(pkg.Verbosity(verbosity))
  14. defer foo.Option(prev)
  15. // ... do some stuff with foo under high verbosity.
  16. }
  1. type GreeterClient interface {
  2. SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
  3. }
  4. type CallOption interface {
  5. before(*callInfo) error
  6. after(*callInfo)
  7. }
  8. // EmptyCallOption does not alter the Call configuration.
  9. type EmptyCallOption struct{}
  10. // TimeoutCallOption timeout option.
  11. type TimeoutCallOption struct {
  12. grpc.EmptyCallOption
  13. Timeout time.Duration
  14. }

Hybrid APIs

  1. // Dial connects to the Redis server at the given network and
  2. // address using the specified options.
  3. func Dial(network, address string, options ...DialOption) (Conn, error)
  4. // NewConn new a redis conn.
  5. func NewConn(c *Config) (cn Conn, err error)

JSON/YAML 配置怎么加载,无法映射 DialOption 啊!”

“嗯,不依赖配置的走 options,配置加载走 config

Configuration & APIs

  1. package redis
  2. // Option configures how we set up the connection.
  3. type Option interface {
  4. apply(*options)
  5. }
  6. // Options apply config to options.
  7. func (c *Config) Options() []redis.Options {
  8. return []redis.Options{
  9. redis.DialDatabase(c.Database),
  10. redis.DialPassword(c.Password),
  11. redis.DialReadTimeout(c.ReadTimeout),
  12. }
  13. }
  14. func main() {
  15. // instead use load yaml file.
  16. c := &Config{
  17. Network: "tcp",
  18. Addr: "127.0.0.1:3389",
  19. Database: 1,
  20. Password: "Hello",
  21. ReadTimeout: 1 * time.Second,
  22. }
  23. r, _ := redis.Dial(c.Network, c.Addr, c.Options()...)
  24. }

Configuration Best Pratice

image.png
代码更改系统功能是一个冗长且复杂的过程,往往还涉及 Review、测试等流程,但更改单个配置选项可能会对功能产生重大影响,通常配置还未经测试。配置的目标:

  • 避免复杂
  • 多样的配置
  • 简单化努力
  • 以基础设施 -> 面向用户进行转变
  • 配置的必选项和可选项
  • 配置的防御编程
  • 权限和变更跟踪
  • 配置的版本和应用对齐
  • 安全的配置变更:逐步部署、回滚更改、自动回滚