乐趣区

关于golang:项目配置文件获取及更新热更新

在我的项目开发中,配置文件的正当的获取和更新是一个根本的性能。我的项目上线后批改配置项,批改配置之后如果还重新启动我的项目能力失效,这样成果并不好。为了可能在不影响我的项目失常运行的状况下批改配置项,就须要用到配置热更新。例如:上线后想要批改日志的级别,能够间接批改配置文件,我的项目主动扫描配置文件,如果发现文件被批改,则从新获取配置信息。读取配置和配置热更新有两种形式:

  • 形式一:调用 github.com/fsnotify/fsnotify 包,监控配置变动,并应用其余的包来读取文件
  • 形式二:调用 github.com/spf13/viper 包,这个包是由 Steve Francia 开发,提供了监控和配置的设置、获取的办法

一、形式一的应用

  • main.go 文件
package main

import (
    "file-store/handler"
    "file-store/models"
    "file-store/utils"
    "fmt"
    "log"
    "net/http"

    "github.com/fsnotify/fsnotify"
)

func main() {
    configPath := "/Users/apple/workplace/file-store/config/config.yaml"
    configDir := "/Users/apple/workplace/file-store/config"

    // 加载配置文件
    _ = utils.InitConfig(configPath, "config")
    fmt.Printf("配置为:%s\n", utils.Conf.Token.Salt)

    watcher, err := fsnotify.NewWatcher()
    if err != nil {log.Fatal(err)
    }
    defer watcher.Close()

    done := make(chan bool)
    go func() {
        for {
            select {
            case event := <-watcher.Events:
                if event.Op&fsnotify.Write == fsnotify.Write {_ = utils.InitConfig(configPath, "config")
                    fmt.Printf("更新配置为:%s\n", utils.Conf.Token.Salt)
                }
            case err := <-watcher.Errors:
                log.Println("error:", err)
            }
        }
    }()
    fmt.Printf("数据库配置为:%s, %s\n", utils.Conf.Mysql.Drive, utils.Conf.Mysql.Address)
    // 数据库操作
    models.MysqlInit(utils.Conf.Mysql.Drive, utils.Conf.Mysql.Address)
    // 监听端口
    err = http.ListenAndServe("127.0.0.1:8000", nil)
    if err != nil {fmt.Printf("Failed to start server, err %s", err.Error())
    }

    // 监控文件
    err = watcher.Add(configPath)
    if err != nil {log.Fatal(err)
    }
    // 监控文件夹
    err = watcher.Add(configDir)
    if err != nil {log.Fatal(err)
    }
    <-done
}
  • config.go:该文件用于获取配置信息,读取配置用到两个包:ioutil 读取配置,gopkg.in/yaml.v2 用于数据的转换
package utils

import (
    "fmt"
    "io/ioutil"
    "sync"

    "gopkg.in/yaml.v2"
)

type Config struct {
    File  *File  `yaml:"file"`
    Mysql *Mysql `yaml:"mysql"`
    Token *Token `yaml:"token"`
}

type File struct {Path string `yaml:"path"`}

type Mysql struct {
    Drive   string `yaml:"drive"`
    Address string `yaml:"address"`
}

type Token struct {
    Salt  string `yaml:"salt"`
    Issue string `yaml:"issue"`
}

var Conf *Config

// InitConfig 读取 yaml 配置文件
func InitConfig(configPath, configName string) error {var locker = new(sync.RWMutex)
    yamlFile, err := ioutil.ReadFile(configPath)
    if err != nil {panic(err)
    }
    locker.Lock()
    err1 := yaml.Unmarshal(yamlFile, &Conf)
    if err1 != nil {panic(err)
    }
    locker.Unlock()
    fmt.Println(Conf.Token.Salt)
    return nil
}

二、形式二的应用

  • config.go 文件
package utils

import (
    "fmt"

    "github.com/spf13/viper"
)

type Config struct {
    File  *File  `yaml:"file"`
    Mysql *Mysql `yaml:"mysql"`
    Token *Token `yaml:"token"`
}

type File struct {Path string `yaml:"path"`}

type Mysql struct {
    Drive   string `yaml:"drive"`
    Address string `yaml:"address"`
}

type Token struct {
    Salt  string `yaml:"salt"`
    Issue string `yaml:"issue"`
}

// 全局配置
var config = new(Config)

// InitConfig 读取 yaml 配置文件
func InitConfig(configPath, configName, configType string) error {viper.SetConfigName(configName) // 配置文件名
    viper.SetConfigType(configType) // 配置文件类型,例如:toml、yaml 等
    viper.AddConfigPath(configPath) // 查找配置文件所在的门路,屡次调用能够增加多个配置文件搜寻的目录
    // 读取配置文件配置,并处理错误
    if err := viper.ReadInConfig(); err != nil {if _, ok := err.(viper.ConfigFileNotFoundError); ok {return err}
    }
    // 监控配置文件变动
    viper.WatchConfig()
    viper.Unmarshal(config)
    if err := validateConfig(config); err != nil {return err}
    return nil
}

// 获取全局配置
func GetConfig() *Config {return config}

// validateConfig:校验配置信息
func validateConfig(conf *Config) error {
    var (
        file    = conf.File.Path
        drive   = conf.Mysql.Drive
        address = conf.Mysql.Address
        salt    = conf.Token.Salt
        issue   = conf.Token.Issue
    )
    if file == "" {return fmt.Errorf("invalid file path: %s\n", file)
    }
    if drive == "" {return fmt.Errorf("invalid drive: %s\n", drive)
    }
    if address == "" {return fmt.Errorf("invalid address: %s\n", address)
    }
    if salt == "" {return fmt.Errorf("invalid salt: %s\n", salt)
    }
    if issue == "" {return fmt.Errorf("invalid issue: %s\n", issue)
    }
    return nil
}
  • main.go 文件
package main

import (
    "file-store/handler"
    "file-store/models"
    "file-store/utils"
    "fmt"
    "net/http"
)

func main() {
    configPath := "/Users/apple/workplace/file-store/config"
    // 配置初始化
    err := utils.InitConfig(configPath, "config", "yaml")
    if err != nil {fmt.Printf("Failed to init config, err is %s\n", err)
    }
    // 获取全局配置
    conf := utils.GetConfig()
    fmt.Println(conf.File.Path)
    // 数据库操作
    models.MysqlInit(conf.Mysql.Drive, conf.Mysql.Address)
    // 监听端口
    err = http.ListenAndServe("127.0.0.1:8000", nil)
    if err != nil {fmt.Printf("Failed to start server, err %s", err.Error())
    }
}
退出移动版