Go 專案配置檔案的定義和讀取

kevinwan發表於2022-05-09

前言

我們在寫應用時,基本都會用到配置檔案,從各種 shellnginx 等,都有自己的配置檔案。雖然這沒有太多難度,但是配置項一般相對比較繁雜,解析、校驗也會比較麻煩。本文就給大家講講我們是怎麼簡化配置檔案的定義和解析的。

場景

如果我們要寫一個 Restful API 的服務,配置項大概有如下內容:

  • Host,偵聽的 IP,如果不填,預設用 0.0.0.0
  • Port,偵聽的埠,必填,只能是數字,大於等於80,小於65535
  • LogMode,日誌模式,只能選 file 或者 console
  • Verbose,看是否輸出詳細日誌,可選,預設為 false
  • MaxConns,允許的最大併發連線數,預設 10000
  • Timeout,超時設定,預設 3s
  • CpuThreshold,設定 CPU 使用率觸發系統降載的閾值,預設 9001000m 表示 100%

之前我們用 json 做配置檔案,但是 json 有個問題,無法加註釋,所以我們後來切換到了 yaml 格式。

接下來讓我們看看藉助 go-zero 怎麼來方便的的定義和解析這樣的配置檔案~

定義配置

首先,我們需要將上述配置需求定義到 Go 結構體裡,如下:

RestfulConf struct {
    Host         string        `json:",default=0.0.0.0"`
    Port         int           `json:",range=[80,65535)"`
    LogMode      string        `json:",options=[file,console]"`
    Verbose      bool          `json:",optional"`
    MaxConns     int           `json:",default=10000"`
    Timeout      time.Duration `json:",default=3s"`
    CpuThreshold int64         `json:",default=900,range=[0:1000]"`
}

可以看到,我們對每個配置項都有一定的定義和限制,其中一些定義如下:

  • default,配置沒填的話,使用該預設值,可以看到其中的 3s 會自動解析成 time.Duration 型別
  • optional,此項可以不配置,沒有的話,用型別零值
  • range,限定數字型別,需要在給定的範圍內
  • options,限制配置的值只能是給出的這幾個之一

並且,一些屬性可以疊加使用,比如:

  • defaultrange 一起使用,就可以既增加了範圍限制,又提供了預設值
  • defaultoptions 一起使用,就可以既增加了可選項限制,又提供了預設值

配置檔案

因為我們在定義配置的時候,給了很多的預設值,還有使用 optional 指定為可選,所以我們的配置檔案裡的配置項就相對比較少了,能用預設值的就不用寫了,如下:

# 因為很多都有預設值,所以只需要寫需要指定值和沒有預設值的
Port: 8080
LogMode: console
# 可以讀取環境變數的值
MaxBytes: ${MAX_BYTES}

這裡有個注意點,如果配置項的 value 全部是數字,而你定義的配置型別是 string,比如有人測試密碼經常用 123456,但是密碼一般會定義為 string,配置就要寫成如下(只是舉個例子哈,密碼一般不建議裸寫到配置檔案裡):

Password: "123456"

這裡的雙引號不能少,少了會報 type mismatch 之類的錯誤,因為 yaml 解析器會把不帶雙引號的 123456 解析成 int

載入配置檔案

我們有了配置定義(config.go)和配置檔案(config.yaml),接下來就是載入配置檔案了,載入配置檔案有三種方式:

  • 必須載入成功,否則程式退出,我們一般這麼用,如果配置不對,程式就無法繼續了
// 有錯誤直接退出程式
var config RestfulConf
conf.MustLoad("config.yaml", &config)

go-zero 自帶的 goctl 生成的預設程式碼也是使用 MustLoad 來載入配置檔案的

  • 載入配置,並自行判斷是否有 error
// 自己判斷並處理 error
var config RestfulConf
// 為了更簡潔,這裡的 LoadConfig 後續會改為 Load,LoadConfig 已被標記為 Deprecated
if err := conf.LoadConfig("config.yaml", &config); err != nil {
    log.Fatal(err)
}
  • 載入配置並讀取環境變數
// 自動讀取環境變數
var config RestfulConf
conf.MustLoad(configFile, &config, conf.UseEnv())

這裡為啥我們需要顯式指定 conf.UseEnv(),因為如果預設讀取的話,可能在配置裡大家寫特定字元的時候就需要 escape 了,所以預設不讀取環境變數,這個設計也歡迎大家多提提建議哈

實現原理

我們在實現類似 yaml/json 解析的時候一般會直接使用 encoding/json 或者對應的 yaml 庫,但是對於 go-zero 來說,我們需要在 unmarshal 的時候有更精確的控制,這就需要我們自己定製 yaml/json 的解析了,完整的程式碼實現在:

配置檔案程式碼:https://github.com/zeromicro/go-zero/tree/master/core/conf

yaml/json 解析程式碼:https://github.com/zeromicro/go-zero/tree/master/core/mapping

這裡也充分展示了 reflect 的用法,以及複雜場景下如何通過單元測試保證程式碼的正確性。

總結

我一直比較推薦 Fail Fast 的思想,我們在載入配置檔案的時候也是這樣,一旦有錯誤,立馬退出,這樣運維在部署服務時就會及時發現問題,因為程式壓根起不來。

go-zero 的所有服務的配置項都是通過這樣的方式來載入和自動驗證的,包括我寫的很多工具的配置也是基於此來實現的,希望能對你有所幫助!

專案地址

https://github.com/zeromicro/go-zero

歡迎使用 go-zerostar 支援我們!

微信交流群

關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。

相關文章