本文主要记录kratos项目配置的定义、读取和源码的学习

at first

服务在启动的基本都会用到配置文件,那么如果是你来写config工具库,它所能提供的功能是什么?

  1. 读取不同场景的配置
  2. 读取不同格式的配置
  3. 支持热更
  4. 支持拓展,提供自定义实现

kratos/config 有那些功能

  1. kratos/config包支持多种配置源,包括
    • 本地文件
    • 本地环境
    • contrib/config支持配置中心
  2. kratos/config 支持多种配置格式
    • json
    • proto
    • xml
    • yaml
  3. 支持热更
  4. 支持其它格式的配置文件

定义配置

在项目目录下创建文件: workspace/configs/config.yaml

service:
  name: config
  version: v1.0.0
http:
  server:
    address: 0.0.0.0:8000
    timeout: 1s
grpc:
  server:
    address: 0.0.0.0:9000
    timeout: 1s

加载配置文件

WithSource

使用withsoure指定数据源为本地文件

package main

import (
	"flag"
	"log"

	"github.com/go-kratos/kratos/v2/config"
	"github.com/go-kratos/kratos/v2/config/file"
)

var flagconf string

func init() {
	flag.StringVar(&flagconf, "conf", "config.yaml", "config path, eg: -conf config.yaml")
}

func main() {
	flag.Parse()
	c := config.New(
		config.WithSource(
			file.NewSource(flagconf),
		),
	)
	if err := c.Load(); err != nil {
		panic(err)
	}

	//定义配置JSON字段
	var v struct {
		Service struct {
			Name    string `json:"name"`
			Version string `json:"version"`
		} `json:"service"`
	}

	//将配置Unmarshal 到struct  
	if err := c.Scan(&v); err != nil {
		panic(err)
	}
	log.Printf("config: %+v", v)

	//获取与该键关联的值
	name, err := c.Value("service.name").String()
	if err != nil {
		panic(err)
	}
	log.Printf("service: %s", name)

	// 热更执行钩子
	if err := c.Watch("service.name", func(key string, value config.Value) {
		log.Printf("config changed: %s = %v\n", key, value)
	}); err != nil {
		panic(err)
	}

	<-make(chan struct{})
}

output:

$  config: {Service:{Name:config Version:v1.0.0}}
$  service: config

WithDecoder

Decoder用于将配置文件内容用特定的反序列化方法解析出来

c := config.New(
		config.WithSource(
			file.NewSource(flagconf),
		),
		config.WithDecoder(func(src *config.KeyValue, target map[string]interface{}) error {
			if src.Format == "" {
				// expand key "aaa.bbb" into map[aaa]map[bbb]interface{}
				keys := strings.Split(src.Key, ".")
				for i, k := range keys {
					if i == len(keys)-1 {
						target[k] = src.Value
					} else {
						sub := make(map[string]interface{})
						target[k] = sub
						target = sub
					}
				}
				return nil
			}
			if codec := encoding.GetCodec(src.Format); codec != nil {
				return codec.Unmarshal(src.Value, &target)
			}
			return fmt.Errorf("unsupported key: %s format: %s", src.Key, src.Format)
		}),
	)

output 和上例一致

原理

// Config is a config interface.
type Config interface {
	Load() error
	Scan(v interface{}) error
	Value(key string) Value
	Watch(key string, o Observer) error
	Close() error
}
  1. 指定数据源的这样会将整个目录中的所有文件进行解析加载,合并到同一个map中
    c := config.New(
            config.WithSource(
                file.NewSource(flagconf),
            ),
        )
        if err := c.Load(); err != nil {
            panic(err)
         }
  1. 使用之前创建好的config实例,调用.Scan方法,读取配置文件的内容到结构体中,这种方式适用于完整获取整个配置文件的内容
    if err := c.Scan(&v); err != nil {
      panic(err)
    }
    fmt.Printf("config: %+v", v)
  1. New的时候会有一个默认的 decoder
func New(opts ...Option) Config {
	o := options{
		logger:   log.DefaultLogger,
		decoder:  defaultDecoder,
		resolver: defaultResolver,
	}
	for _, opt := range opts {
		opt(&o)
	}
	return &config{
		opts:   o,
		reader: newReader(o),
		log:    log.NewHelper(o.logger),
	}
}

通过option可以拓展自定义 decoder

func WithDecoder(d Decoder) Option {
	return func(o *options) {
		o.decoder = d
	}
}

参考

  1. https://github.com/go-kratos/kratos/v2/config
  2. https://github.com/go-kratos/kratos/tree/main/contrib/config
  3. https://go-kratos.dev/docs/component/config