Golang學習--TOML配置處理

上一篇文章中咱們學會了使用包管理工具,這樣咱們就能夠很方便的使用包管理工具來管理咱們依賴的包。php

配置工具的選擇

但咱們又遇到了一個問題,一個項目一般是有不少配置的,好比PHP的php.ini文件、Nginx的server.conf文件,那麼Golang的項目又適合使用怎樣的配置文件呢?html

其實如今咱們有不少選擇,好比 JSON文件、INI文件、YAML文件和TOML文件等等。git

其中這些文件,對應的Golang處理庫以下:github

  • encoding/json -- 標準庫中的包,能夠處理JSON配置文件,缺點是不能加註釋
  • gcfg -- 處理INI配置文件
  • toml -- 處理TOML配置文件
  • viper -- 處理JSON, TOML, YAML, HCL以及Java properties配置文件

其實關於怎麼選擇能夠看看stackoverflow上的問題How to handle configuration in Gogolang

toml的使用

我根據本身的喜愛選了toml,下面就來講下toml。shell

先來看一個TOML文件的例子:json

# 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代碼中如何使用ide

咱們基於上面的配置文件來定義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中的信號處理

相關文章
相關標籤/搜索