go微服務框架kratos學習筆記五(kratos 配置中心 paladin config sdk [斷劍重鑄之日,騎士歸來之時])


go微服務框架kratos學習筆記五(kratos 配置中心 paladin config sdk [斷劍重鑄之日,騎士歸來之時])


本節看看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:\VSProject\kratos-note\paladin\paladin>cd cmd

I:\VSProject\kratos-note\paladin\paladin\cmd>kratos build
directory: I:\VSProject\kratos-note\paladin\paladin/cmd
kratos: 0.3.1
build success.

I:\VSProject\kratos-note\paladin\paladin\cmd>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:\VSProject\kratos-note\paladin\paladin\cmd>

會發現直接運行時跑不起來的,因為這時候找不到配置文件,因為這時候我們沒有調用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:\VSProject\kratos-note\paladin\paladin\cmd>cmd.exe -conf=I:\VSProject\kratos-note\paladin\paladin\configs
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:\VSProject\kratos-note\paladin\paladin\configs
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使用舒適度還是挺不錯的、

斷劍重鑄之日,騎士歸來之時


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM