文章來源:Golang學習--TOML配置處理
上一篇文章中我們學會了使用包管理工具,這樣我們就可以很方便的使用包管理工具來管理我們依賴的包。
配置工具的選擇
但我們又遇到了一個問題,一個專案通常是有很多配置的,比如PHP的php.ini檔案、Nginx的server.conf檔案,那麼Golang的專案又適合使用怎樣的配置檔案呢?
其實現在我們有很多選擇,比如 JSON檔案、INI檔案、YAML檔案和TOML檔案等等。
其中這些檔案,對應的Golang處理庫如下:
- encoding/json -- 標準庫中的包,可以處理JSON配置檔案,缺點是不能加註釋
- gcfg -- 處理INI配置檔案
- toml -- 處理TOML配置檔案
- viper -- 處理JSON, TOML, YAML, HCL以及Java properties配置檔案
其實關於怎麼選擇可以看看stackoverflow上的問題How to handle configuration in Go。
toml的使用
我根據自己的喜好選了toml,下面就來說下toml。
先來看一個TOML檔案的例子:
# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
大家可以看到這裡的格式非常靈活,可以是數字、字串、布林等簡單型別,也可以是陣列、map等等複雜的型別。
關於具體的TOML語言的解說大家檢視文件 toml-lang/toml
下面我們再來說一下,具體的Golang程式碼中如何使用
我們基於上面的配置檔案來定義Golang中配置的struct,如下:
type tomlConfig struct {
Title string
Owner ownerInfo
DB database `toml:"database"`
Servers map[string]server
Clients clients
}
type ownerInfo struct {
Name string
Org string `toml:"organization"`
Bio string
DOB time.Time
}
type database struct {
Server string
Ports []int
ConnMax int `toml:"connection_max"`
Enabled bool
}
type server struct {
IP string
DC string
}
type clients struct {
Data [][]interface{}
Hosts []string
}
這一些都定義好之後,我們只需要將檔案配置中的內容轉成Golang中可用的struct例項即可,程式碼如下:
var config tomlConfig
filePath := "/your/path/config.toml"
if _, err := toml.DecodeFile(filePath, &config); err != nil {
panic(err)
}
這樣我們拿到的config就是擁有TOML檔案內容的tomlConfig的例項,可以直接使用。
配置的單例模式
通常來說,在一個專案中,配置檔案只需要解析一次,所以可以使用單例模式包一下config的解析。
程式碼如下:
package config
var (
cfg * tomlConfig
once sync.Once
)
func Config() *tomlConfig {
once.Do(func() {
filePath, err := filepath.Abs("./ch3/config.toml")
if err != nil {
panic(err)
}
fmt.Printf("parse toml file once. filePath: %s\n", filePath)
if _ , err := toml.DecodeFile(filePath, &cfg); err != nil {
panic(err)
}
})
return cfg
}
這裡我們使用了sync.Once的Do方法,Do方法當且僅當第一次被呼叫時才執行函式。
如果once.Do(f)被多次呼叫,只有第一次呼叫會執行f,即使f每次呼叫Do 提供的f值不同。需要給每個要執行僅一次的函式都建立一個Once型別的例項。
這樣我們就保證了tomlConfig物件是一個單例模式,只需要解析一次,可以在任何地方呼叫。呼叫例子如下:
// 配置中DB的IP
fmt.Println(config.Config().DB.Server)
// 配置中Owner的名字
fmt.Println(config.Config().Owner.Name)
配置的更新
如果我們的專案是一個常駐的專案(比如http server),我們會希望能夠提供更新配置的功能,平滑的替換掉配置,不需要重啟專案。
其實思路很想簡單,我們只需要起一個協程,監視我們定義好的訊號,如果接收到訊號就重新載入配置。
下面我們來寫下,更新配置的程式碼:
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGUSR1)
go func() {
for {
<-s
config.ReloadConfig()
log.Println("Reloaded config")
}
}()
我們監視了syscall.SIGUSR1訊號,其值是30,接收到訊號就執行config.ReloadConfig()方法。
再來看下config中方法變動:
func Config() *tomlConfig {
once.Do(ReloadConfig)
cfgLock.RLock()
defer cfgLock.RUnlock()
return cfg
}
func ReloadConfig() {
filePath, err := filepath.Abs("./ch3/config.toml")
if err != nil {
panic(err)
}
fmt.Printf("parse toml file once. filePath: %s\n", filePath)
config := new(tomlConfig)
if _ , err := toml.DecodeFile(filePath, config); err != nil {
panic(err)
}
cfgLock.Lock()
defer cfgLock.Unlock()
cfg = config
}
原來載入配置的程式碼放到ReloadConfig方法中去了,還在給變數cfg賦值的時候加了讀寫鎖,以保證安全。在Config方法中獲取cfg的時候加了讀鎖,防止在讀的時候,也在寫入,導致配置錯亂。
啟動server之後,可以通過如下shell命令更新配置
kill -SIGUSR1 6666
其中的6666是go server的程式號。執行這條命令之後,會向go server傳送syscall.SIGUSR1的訊號,從而觸發更新配置的動作。
POSIX訊號
這邊順便列一下POSIX中定義的訊號:
Linux 使用34-64訊號用作實時系統中。
命令 man 7 signal 提供了官方的訊號介紹。
在POSIX.1-1990標準中定義的訊號列表:
訊號 | 值 | 動作 | 說明 |
---|---|---|---|
SIGHUP | 1 | Term | 終端控制程式結束(終端連線斷開) |
SIGINT | 2 | Term | 使用者傳送INTR字元(Ctrl+C)觸發 |
SIGQUIT | 3 | Core | 使用者傳送QUIT字元(Ctrl+/)觸發 |
SIGILL | 4 | Core | 非法指令(程式錯誤、試圖執行資料段、棧溢位等) |
SIGABRT | 6 | Core | 呼叫abort函式觸發 |
SIGFPE | 8 | Core | 算術執行錯誤(浮點運算錯誤、除數為零等) |
SIGKILL | 9 | Term | 無條件結束程式(不能被捕獲、阻塞或忽略) |
SIGSEGV | 11 | Core | 無效記憶體引用(試圖訪問不屬於自己的記憶體空間、對只讀記憶體空間進行寫操作) |
SIGPIPE | 13 | Term | 訊息管道損壞(FIFO/Socket通訊時,管道未開啟而進行寫操作) |
SIGALRM | 14 | Term | 時鐘定時訊號 |
SIGTERM | 15 | Term | 結束程式(可以被捕獲、阻塞或忽略) |
SIGUSR1 | 30,10,16 | Term | 使用者保留 |
SIGUSR2 | 31,12,17 | Term | 使用者保留 |
SIGCHLD | 20,17,18 | Ign | 子程式結束(由父程式接收) |
SIGCONT | 19,18,25 | Cont | 繼續執行已經停止的程式(不能被阻塞) |
SIGSTOP | 17,19,23 | Stop | 停止程式(不能被捕獲、阻塞或忽略) |
SIGTSTP | 18,20,24 | Stop | 停止程式(可以被捕獲、阻塞或忽略) |
SIGTTIN | 21,21,26 | Stop | 後臺程式從終端中讀取資料時觸發 |
SIGTTOU | 22,22,27 | Stop | 後臺程式向終端中寫資料時觸發 |
在SUSv2和POSIX.1-2001標準中的訊號列表:
訊號 | 值 | 動作 | 說明 |
---|---|---|---|
SIGTRAP | 5 | Core | Trap指令觸發(如斷點,在偵錯程式中使用) |
SIGBUS | 0,7,10 | Core | 非法地址(記憶體地址對齊錯誤) |
SIGPOLL | Term | Pollable event (Sys V). Synonym for SIGIO | |
SIGPROF | 27,27,29 | Term | 效能時鐘訊號(包含系統呼叫時間和程式佔用CPU的時間) |
SIGSYS | 12,31,12 | Core | 無效的系統呼叫(SVr4) |
SIGURG | 16,23,21 | Ign | 有緊急資料到達Socket(4.2BSD) |
SIGVTALRM | 26,26,28 | Term | 虛擬時鐘訊號(程式佔用CPU的時間)(4.2BSD) |
SIGXCPU | 24,24,30 | Core | 超過CPU時間資源限制(4.2BSD) |
SIGXFSZ | 25,25,31 | Core | 超過檔案大小資源限制(4.2BSD) |
程式碼可參考:https://github.com/CraryPrimitiveMan/go-in...
參考資料
Design Patterns in Golang: Singleton
Golang hot configuration reload
Golang中的訊號處理
本作品採用《CC 協議》,轉載必須註明作者和本文連結