Kratos 配置

2022-04-24 11:43 更新

配置

微服務(wù)或者說云原生應(yīng)用的配置最佳實踐是將配置文件和應(yīng)用代碼分開管理——不將配置文件放入代碼倉庫,也不打包進容器鏡像,而是在服務(wù)運行時,把配置文件掛載進去或者直接從配置中心加載。Kratos的config組件就是用來幫助應(yīng)用從各種配置源加載配置。

設(shè)計理念

1.支持多種配置源

Kratos定義了標準化的Source和Watcher接口來適配各種配置源。

框架內(nèi)置了本地文件file和環(huán)境變量env的實現(xiàn)。

另外,在contrib/config下面,我們也提供了如下的配置中心的適配供使用:

  • apollo
  • consul
  • etcd
  • kubernetes
  • nacos

如果上述的配置加載方式無法涵蓋您的環(huán)境,您也可以通過實現(xiàn)接口來適配您自己的配置加載方式。

2.支持多種配置格式

配置組件復(fù)用了?encoding?中的反序列化邏輯作為配置解析使用。默認支持以下格式的解析:

  • json
  • proto
  • xml
  • yaml

框架將根據(jù)配置文件類型匹配對應(yīng)的Codec,進行配置文件的解析。您也可以通過實現(xiàn)Codec并用?encoding.RegisterCodec?方法,將它注冊進去,來解析其它格式的配置文件。

配置文件類型的提取,根據(jù)配置源具體實現(xiàn)不同而略有區(qū)別,內(nèi)置的file是把文件后綴作為文件類型的,其它配置源插件的具體邏輯請參考對應(yīng)的文檔。

3.熱更新

Kratos的config組件支持配置的熱更新,您可以使用配置中心配合config的熱更新功能,在服務(wù)不重新發(fā)布/不停機/不重啟的情況下,在線更新服務(wù)的配置,修改服務(wù)的一些行為。

4.配置合并

在config組件中,所有的配置源中的配置(文件)將被逐個讀出,分別解析成map,并合并到一個map中去。因此在加載完畢后,不需要再理會配置的文件名,不用文件名來進行查找,而是用內(nèi)容中的結(jié)構(gòu)來對配置的值進行索引即可。設(shè)計和編寫配置文件時,請注意各個配置文件中,根層級的key不要重復(fù),否則可能會被覆蓋。

舉例:

有如下兩個配置文件:

# 文件1
foo:
  baz: "2"
  biu: "example"
hello:
  a: b
# 文件2
foo:
  bar: 3
  baz: aaaa
hey:
  good: bad
  qux: quux

?.Load?后,將被合并為如下的結(jié)構(gòu):

{
  "foo": {
    "baz": "aaaa",
    "bar": 3,
    "biu": "example"
  },
  "hey": {
    "good": "bad",
    "qux": "quux"
  },
  "hello": {
    "a": "b"
  }
}

我們可以發(fā)現(xiàn),配置文件的各層級將分別合并,在key沖突時會發(fā)生覆蓋,而具體的覆蓋順序,會由配置源實現(xiàn)中的讀取順序決定,因此這里重新提醒一下,各個配置文件中,根層級的key不要重復(fù),也不要依賴這個覆蓋的特性,從根本上避免不同配置文件的內(nèi)容互相覆蓋造成問題。

在使用時,可以用?.Value("foo.bar")?直接獲取某個字段的值,也可以用?.Scan?方法來將整個map讀進某個結(jié)構(gòu)體中,具體使用方式請看下文。

使用

1.初始化配置源

使用file,即從本地文件加載: 這里的path就是配置文件的路徑,這里也可以填寫一個目錄名,這樣會將整個目錄中的所有文件進行解析加載,合并到同一個map中。

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

path := "configs/config.yaml"
c := config.New(
    config.WithSource(
        file.NewSource(path),
    )
)

如果想用外部的配置中心,可以在contrib/config里面找一個,以consul為例:

import (
    "github.com/go-kratos/kratos/contrib/config/consul/v2"
    "github.com/hashicorp/consul/api"
)

consulClient, err := api.NewClient(&api.Config{
  Address: "127.0.0.1:8500",
})
if err != nil {
  panic(err)
}
cs, err := consul.New(consulClient, consul.WithPath("app/cart/configs/"))
if err != nil {
  panic(err)
}
c := config.New(config.WithSource(cs))

不同的配置源插件使用方式略有差別,您可以參考它們各自的文檔或examples。

2.讀取配置

首先要定義一個結(jié)構(gòu)體用來解析字段,如果您使用的是kratos-layout創(chuàng)建的項目,可以參考后面講解kratos-layout的部分,使用proto文件定義配置和生成struct。

我們這里演示的是手工定義結(jié)構(gòu),您需要在結(jié)構(gòu)體上用json tag來定義您配置文件的字段。

var v struct {
  Service struct {
    Name    string `json:"name"`
    Version string `json:"version"`
  } `json:"service"`
}

使用之前創(chuàng)建好的config實例,調(diào)用.Scan方法,讀取配置文件的內(nèi)容到結(jié)構(gòu)體中,這種方式適用于完整獲取整個配置文件的內(nèi)容。

// Unmarshal the config to struct
if err := c.Scan(&v); err != nil {
  panic(err)
}
fmt.Printf("config: %+v", v)

使用config實例的.Value方法,可以單獨獲取某個字段的內(nèi)容。

name, err := c.Value("service.name").String()
if err != nil {
  panic(err)
}
fmt.Printf("service: %s", name)

3.監(jiān)聽配置變更

通過?.Watch?方法,可以監(jiān)聽配置中某個字段的變更,在本地或遠端的配置中心有配置文件變更時,執(zhí)行回調(diào)函數(shù)進行自定義的處理

if err := c.Watch("service.name", func(key string, value config.Value) {
  fmt.Printf("config changed: %s = %v\n", key, value)
  // 在這里寫回調(diào)的邏輯
}); err != nil {
  log.Error(err)
}

4.讀取環(huán)境變量

如果有配置需要從環(huán)境變量讀取,請使用以下方式:

配置環(huán)境變量配置源env:

c := config.New(
    config.WithSource(
        // 添加前綴為 KRATOS_ 的環(huán)境變量,不需要的話也可以設(shè)為空字符串
        env.NewSource("KRATOS_"),
        // 添加配置文件
        file.NewSource(path),
    ))
    
// 加載配置源:
if err := c.Load(); err != nil {
    log.Fatal(err)
}

// 獲取環(huán)境變量 KRATOS_PORT 的值,這里用去掉前綴的名稱進行讀取
port, err := c.Value("PORT").String()

除了上面使用Value方法直接讀的方式,也可以在配置文件內(nèi)容里使用占位符來把環(huán)境變量中的值渲染進去:

service:
  name: "kratos_app"
http:
  server:
    # 使用 service.name 的值
    name: "${service.name}"
    # 使用環(huán)境變量 PORT 替換,若不存在,使用默認值 8080
    port: "${PORT:8080}"
    # 使用環(huán)境變量 TIMEOUT 替換,無默認值
    timeout: "$TIMEOUT"

5.配置解析Decoder

Decoder用于將配置文件內(nèi)容用特定的反序列化方法解析出來,默認decoder會根據(jù)文件的類型自動識別類型并解析,通常情況不需要自定義這個,您可以通過后文的實現(xiàn)Codec的方式來注冊更多文件類型。

在初始化config時加入?WithDecoder?參數(shù),可以將Decoder覆蓋為自定義的邏輯。如下代碼展示了配置自定義Decoder的方法,這里使用了yaml庫解析所有配置文件,您可以使用這種方式來使用特定的配置文件解析方法,但更推薦使用后文的實現(xiàn)Codec的方式,能同時支持多種格式的解析。

import "gopkg.in/yaml.v2"

c := config.New(
  config.WithSource(
    file.NewSource(flagconf),
  ),
  config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
    return yaml.Unmarshal(kv.Value, v)
  }),
)

6.配置處理Resolver

Resolver用于對解析完畢后的map結(jié)構(gòu)進行再次處理,默認resolver會對配置中的占位符進行填充。您可以通過在初始化config時加入?WithResolver?參數(shù),來覆蓋resolver的行為。

c := config.New(
  config.WithSource(
    file.NewSource(flagconf),
  ),
  config.WithResolver(func (input map[string]interface{}) (err error)  {
    // 在這里對input進行處理即可
    // 您可能需要定義一個遞歸的函數(shù),來處理嵌套的map結(jié)構(gòu)
    return 
  }),
)

7.支持其它格式的配置文件

首先實現(xiàn)Codec,這里以yaml為例

import (
    "github.com/go-kratos/kratos/v2/encoding"
    "gopkg.in/yaml.v3"
)

const Name = "myyaml"

func init() {
    encoding.RegisterCodec(codec{})
}

// codec is a Codec implementation with yaml.
type codec struct{}

func (codec) Marshal(v interface{}) ([]byte, error) {
    return yaml.Marshal(v)
}

func (codec) Unmarshal(data []byte, v interface{}) error {
    return yaml.Unmarshal(data, v)
}

func (codec) Name() string {
    return Name
}

然后注冊該Codec 這里由于我們把注冊代碼?encoding.RegisterCodec(codec{})?寫在了包的?init?方法中,所以在包被import的時候,將會運行這個?init?方法,也就是進行注冊。所以您可以在代碼入口(比如?main.go?)對它進行注冊

import _ "path/to/your/codec"

隨后,config組件就能把上面代碼中?const Name = "myyaml"?這部分作為格式類型名,調(diào)用該Codec解析這個文件。

kratos-layout

理念

1.項目結(jié)構(gòu)

layout中涉及到配置文件有以下部分,簡單介紹一下它們的作用

  • cmd/server/main.go 這個是服務(wù)的入口,我們默認使用了內(nèi)置的config/file組件從本地文件系統(tǒng)讀取配置文件,默認會讀取相對路徑?configs?目錄,您可以修改這個文件里?config.New()?參數(shù)中使用的配置源,從其它配置源(比如配置中心)進行加載配置。配置在這里將被加載到?conf.Bootstrap?結(jié)構(gòu)體中,這個結(jié)構(gòu)體的內(nèi)容可以通過依賴注入,注入到服務(wù)內(nèi)部的其它層,比如server或data,這樣各層就能讀取到各自需要的配置,完成自己的初始化。
  • configs/config.yaml 這是一個示例配置文件,configs目錄的內(nèi)容通常不參與服務(wù)的生產(chǎn)環(huán)境運行,您可以用它來進行本地開發(fā)時的配置文件的加載,方便應(yīng)用能本地能跑起來調(diào)試,不要將生產(chǎn)環(huán)境的配置放在這里。
  • internal/conf 在這里放配置文件的結(jié)構(gòu)定義,我們在這里使用?.proto?文件來進行配置定義,然后通過在根目錄執(zhí)行?make config?,就可以將對應(yīng)?.pb.go?文件生成到相同目錄下供使用。在初始狀態(tài)下,這個?conf.proto?所定義的結(jié)構(gòu),就是?configs/config.yaml?的結(jié)構(gòu),請保持兩者一致。
  • make config Makefile中的這個指令,用于生成?.proto?定義的配置對應(yīng)的?.pb.go?文件(就是調(diào)了一下protoc),要記得每次修改定義后,一定要執(zhí)行這個指令來重新生成go文件

2.配置生成命令

我們已經(jīng)把根據(jù)proto生成結(jié)構(gòu)體的指令預(yù)置在Makefile里面了,通過在項目根目錄下執(zhí)行?make config?即可生成。它實際上是調(diào)用了?protoc?工具,掃描internal目錄下的proto文件進行生成。

3.使用Protobuf定義配置

正如前文所說,我們可以在代碼中直接用struct來定義配置結(jié)構(gòu)進行解析。但您可能會發(fā)現(xiàn),我們的最佳實踐項目模板kratos-layout中采用了Protobuf來定義配置文件的結(jié)構(gòu)。通過Protobuf定義,我們可以同時支持多種格式如?json?、?xml?或者?yaml?等多種配置格式統(tǒng)一解析,這樣在讀配置時會變得非常方便。

layout中使用了如下的?.proto?文件定義配置文件的字段:

syntax = "proto3";
package kratos.api;

option go_package = "github.com/go-kratos/kratos-layout/internal/conf;conf";

import "google/protobuf/duration.proto";

message Bootstrap {
  Server server = 1;
}

message Server {
  message HTTP {
    string network = 1;
    string addr = 2;
    google.protobuf.Duration timeout = 3;
  }
  message GRPC {
    string network = 1;
    string addr = 2;
    google.protobuf.Duration timeout = 3;
  }
  HTTP http = 1;
  GRPC grpc = 2;
}

我們可以看出,Protobuf的定義結(jié)構(gòu)清晰,并且可以指定字段的類型,這在后續(xù)的配置文件解析中可以起到校驗的作用,保證加載配置文件的有效性。

在定義好結(jié)構(gòu)后,我們需要用?protoc?工具來生成對應(yīng)的?.pb.go?代碼,也就是相應(yīng)的Go struct和序列化反序列化代碼,供我們使用。

使用

1.定義

修改?internal/conf/config.proto?文件的內(nèi)容,在這里使用Protobuf IDL定義你配置文件的結(jié)構(gòu)。您也可以在這個目錄下創(chuàng)建新的proto文件來定義額外的配置格式。

2.生成

在項目根目錄執(zhí)行下面的命令即可生成用來解析配置文件的結(jié)構(gòu)體:

make config

執(zhí)行成功后,您應(yīng)該能看到?config.pb.go?生成在?config.proto?文件的旁邊,您就可以使用里面的結(jié)構(gòu)體,比如?Bootstrap?來讀取您的配置。

3.使用

讀取配置項、監(jiān)聽配置變更和其它高級用法等使用方面的內(nèi)容,與前文介紹的一致,這里就不再贅述。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號