go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时]

go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])

目录go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])静态配置flag注入在线热加载配置远程配置中心


本节看看kratos的配置中心paladin(骑士)。
kratos对配置文件进行了梳理,配置管理模块化,如redis有redis的单独配置文件、bm有bm的单独配置文件,及为方便易用。

paladin 本质是一个config SDK客户端,包括了remote、file、mock几个抽象功能,方便使用本地文件或者远程配置中心,并且集成了对象自动reload功能。

现在看看paladin的几种配置方式 :

静态配置

照常 new 一个demo项目.

kratos new paladin

随便找个配置,看目录结构都知道http.toml在configs下,可以直接用名字get到,应该是kratos工具做了封装。

http.toml

[Server]
    addr = "0.0.0.0:8000"
    timeout = "1s"
// New new a bm server.
func New(s pb.DemoServer) (engine *bm.Engine, err error) {
	var (
		cfg bm.ServerConfig
		ct paladin.TOML
	)
	if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil {
		return
	}
	if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {
		return
	}

Get() 取到的是个Value结构,利用了encoding包(encoding包定义了供其它包使用的可以将数据在字节水平和文本表示之间转换的接口)做抽象接口。

// Value is config value, maybe a json/toml/ini/string file.
type Value struct {
	val   interface{}
	slice interface{}
	raw   string
}

// Unmarshal is the interface implemented by an object that can unmarshal a textual representation of itself.
func (v *Value) Unmarshal(un encoding.TextUnmarshaler) error {
	text, err := v.Raw()
	if err != nil {
		return err
	}
	return un.UnmarshalText([]byte(text))
}

// UnmarshalTOML unmarhsal toml to struct.
func (v *Value) UnmarshalTOML(dst interface{}) error {
	text, err := v.Raw()
	if err != nil {
		return err
	}
	return toml.Unmarshal([]byte(text), dst)
}

直接kratos run的话,默认是读取的configs下的本地文件。 kratos/tool/run.go 里面是可以找到的.

package main

import (
	"os"
	"os/exec"
	"path"
	"path/filepath"

	"github.com/urfave/cli"
)

func runAction(c *cli.Context) error {
	base, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	dir := buildDir(base, "cmd", 5)
	conf := path.Join(filepath.Dir(dir), "configs")
	args := append([]string{"run", "main.go", "-conf", conf}, c.Args()...)
	cmd := exec.Command("go", args...)
	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		panic(err)
	}
	return nil
}

flag注入

如果我们进行了build

I:VSProjectkratos-notepaladinpaladin>cd cmd

I:VSProjectkratos-notepaladinpaladincmd>kratos build
directory: I:VSProjectkratos-notepaladinpaladin/cmd
kratos: 0.3.1
build success.

I:VSProjectkratos-notepaladinpaladincmd>cmd.exe
INFO 12/30-22:25:07.054 I:/VSProject/kratos-note/paladin/paladin/cmd/main.go:19 paladin start
panic: lack of remote config center args

goroutine 1 [running]:
github.com/bilibili/kratos/pkg/conf/paladin.Init(0x0, 0x0, 0x0, 0x0, 0x0)
        I:/VSProject/go/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/pkg/conf/paladin/default.go:32 +0x25f
main.main()
        I:/VSProject/kratos-note/paladin/paladin/cmd/main.go:20 +0x103

I:VSProjectkratos-notepaladinpaladincmd>

会发现直接运行时跑不起来的,因为这时候找不到配置文件,因为这时候我们没有调用kratos run,paladin找不到配置目录。

实际paladin里面会有一个confPath变量,主函数做paladin.init()的时候会做flag注入。也方便了开发环境开发人员自行做配置修改。

package paladin

import (
	"context"
	"errors"
	"flag"
)

var (
	// DefaultClient default client.
	DefaultClient Client
	confPath      string
)

func init() {
	flag.StringVar(&confPath, "conf", "", "default config path")
}

// Init init config client.
// If confPath is set, it inits file client by default
// Otherwise we could pass args to init remote client
// args[0]: driver name, string type
func Init(args ...interface{}) (err error) {
	if confPath != "" {
		DefaultClient, err = NewFile(confPath)
	} else {
		var (
			driver Driver
		)
		
   ......
I:VSProjectkratos-notepaladinpaladincmd>cmd.exe -conf=I:VSProjectkratos-notepaladinpaladinconfigs
INFO 12/30-22:41:43.717 I:/VSProject/kratos-note/paladin/paladin/cmd/main.go:19 paladin start
2019/12/30 22:41:43 start watch filepath: I:VSProjectkratos-notepaladinpaladinconfigs
INFO 12/30-22:41:43.781 I:/VSProject/go/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/pkg/net/http/blademaster/server.go:98 blademaster: start http listen addr: 0.0.0.0:8000
[warden] config is Deprecated, argument will be ignored. please use -grpc flag or GRPC env to configure warden server.
INFO 12/30-22:41:43.790 I:/VSProject/go/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/pkg/net/rpc/warden/server.go:329 warden: start grpc listen addr: [::]:9000

在线热加载配置

在线读取、变更的配置信息,比如某个业务开关,实现配置reload实时更新。

// Map is config map, key(filename) -> value(file).
type Map struct {
	values atomic.Value
}

paladin.Map 通过 atomic.Value 自动热加载

# service.go
type Service struct {
	ac *paladin.Map
}

func New() *Service {
	// paladin.Map 通过atomic.Value支持自动热加载
	var ac = new(paladin.TOML)
	if err := paladin.Watch("application.toml", ac); err != nil {
		panic(err)
	}
	s := &Service{
		ac: ac,
	}
	return s
}

func (s *Service) Test() {
	sw, err := s.ac.Get("switch").Bool()
	if err != nil {
		// TODO
	}
	
	// or use default value
	sw := paladin.Bool(s.ac.Get("switch"), false)
}

测试

测试一下自动热加载,可以看到demo里面已经有添加了application.toml的自动热加载了

 if err := paladin.Watch("application.toml", ac); err != nil {
  panic(err)
 }

关于watch

可以看到watch里面有个协程在监视变动情况。


// Watch watch on a key. The configuration implements the setter interface, which is invoked when the configuration changes.
func Watch(key string, s Setter) error {
	v := DefaultClient.Get(key)
	str, err := v.Raw()
	if err != nil {
		return err
	}
	if err := s.Set(str); err != nil {
		return err
	}
	go func() {
		for event := range WatchEvent(context.Background(), key) {
			s.Set(event.Value)
		}
	}()
	return nil
}

我们写个get()接口,打印下app.toml的keys() 看看是否会热加载。

func (s *Service) Get(ctx context.Context, req *pb.Req) (reply *pb.Resp, err error) {
	
	log.Info("app toml : (%v)", s.ac.Keys())

app.toml

# This is a TOML document. Boom~
demoExpire = "24h"

[app]
addr = ["127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"]

接着随便加个字段,再次调用get()可以看ttt到加了进来

# This is a TOML document. Boom~
demoExpire = "24h"

[app]
addr = ["127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"]

[ttt]
xixi = "haha"

远程配置中心

通过环境变量注入,例如:APP_ID/DEPLOY_ENV/ZONE/HOSTNAME,然后通过paladin实现远程配置中心SDK进行配合使用。

目前只可以看到这个步骤是在Init()的时候做的,paladin本质是个客户端包,在不知道服务端实现的情况下暂时没找到样例,有机会遇见再补上。

// Init init config client.
// If confPath is set, it inits file client by default
// Otherwise we could pass args to init remote client
// args[0]: driver name, string type
func Init(args ...interface{}) (err error) {
	if confPath != "" {
		DefaultClient, err = NewFile(confPath)
	} else {
		var (
			driver Driver
		)
		argsLackErr := errors.New("lack of remote config center args")
		if len(args) == 0 {
			panic(argsLackErr.Error())
		}
		argsInvalidErr := errors.New("invalid remote config center args")
		driverName, ok := args[0].(string)
		if !ok {
			panic(argsInvalidErr.Error())
		}
		driver, err = GetDriver(driverName)
		if err != nil {
			return
		}
		DefaultClient, err = driver.New()
	}
	if err != nil {
		return
	}
	return
}

体感paladin使用舒适度还是挺不错的、

断剑重铸之日,骑士归来之时

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注