上一篇文章中咱們學會了使用包管理工具,這樣咱們就能夠很方便的使用包管理工具來管理咱們依賴的包。php
但咱們又遇到了一個問題,一個項目一般是有不少配置的,好比PHP的php.ini文件、Nginx的server.conf文件,那麼Golang的項目又適合使用怎樣的配置文件呢?html
其實如今咱們有不少選擇,好比 JSON文件、INI文件、YAML文件和TOML文件等等。git
其中這些文件,對應的Golang處理庫以下:github
其實關於怎麼選擇能夠看看stackoverflow上的問題How to handle configuration in Go。golang
我根據本身的喜愛選了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中定義的信號:
Linux 使用34-64信號用做實時系統中。
命令 man 7 signal 提供了官方的信號介紹。
信號 | 值 | 動做 | 說明 |
---|---|---|---|
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 | 後臺程序向終端中寫數據時觸發 |
信號 | 值 | 動做 | 說明 |
---|---|---|---|
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中的信號處理