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中的信号处理

相关文章
相关标签/搜索