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-action/tree/master/ch3

參考資料

Design Patterns in Golang: Singleton
Golang hot configuration reload
Golang中的信號處理


免責聲明!

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



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