go配置文件热加载

参考:go 配置文件的热加载 - RandolphCYG的记录 - OSCHINA - 中文开源技术交流社区

go 配置文件的热加载

原创

RandolphCYG

之前写的 gin 项目中也用到了 viper 去监控配置文件的热加载,实际上后面部署用 k8s 后,这个功能实际上没啥用了。

  • 但是对于不是 web 的一些小项目,可以做到快速复用,省时省力。
  • 重读在 gin 中写的配置文件热加载功能,发现实际上 watchConfig 方法只是输出了对配置文件的操作,实际上没有将配置文件反序列化到 go 的结构体变量中。
  • 还有之前用 pflag 获取输入的参数,这里就是配置文件的路径,其实一直没用过这个功能,仅仅是留空让判断配置文件路径的逻辑走到默认配置文件名称那里。这边也用 flag 替换加上了。

目录结构

go配置文件热加载_第1张图片

├── dynamicConfig
│   ├── conf
│   ├── go.mod
│   ├── go.sum
│   └── main.go

1. 配置文件 dynamicConfig/conf/conf.yaml

这个配置文件还可以复制一份到其他地方,例如我这边示例复制到了 /Users/randolph/Downloads/test.yaml,用来测试指定配置文件路径情况下,修改配置文件是否有效。

system:
  Mode: debug
  Addr: 127.0.0.1:8099
  Debug: true

database:
  Type: mysql
  UserName: root
  Password: adfgsf^&^*Y
  Addr: 192.168.99.9:3306
  Name: TYT
  ShowLog: true                   # 是否打印SQL日志
  MaxIdleConn: 10                 # 最大闲置的连接数,0意味着使用默认的大小2, 小于0表示不使用连接池
  MaxOpenConn: 60                 # 最大打开的连接数, 需要小于数据库配置中的max_connections数
  ConnMaxLifeTime: 60m            # 单个连接最大存活时间,建议设置比数据库超时时长(wait_timeout)稍小一些

redis:
  Addr: 127.0.0.1:6379
  Password: ""
  DB: 0
  PoolSize: 12000
  DialTimeout: 60s
  ReadTimeout: 500ms
  WriteTimeout: 500ms

ldapCfg:
  ConnUrl:       ldap://192.168.8.8:386
  BaseDn:        DC=good-oc,DC=com
  AdminAccount:  CN=admin,CN=Users,DC=good-oc,DC=com
  Password:      dafg:>^&adsg657&^
  SslEncryption: False
  Timeout:       6

email:
  Host: smtphz.qiye.163.com             # SMTP地址
  Port: 25                              # 端口
  Username: [email protected]              # 用户名
  Password: asf%^GFGFV&y6&y             # 密码
  NickName: UYT                         # 发送者名称
  Address: [email protected]               # 发送者邮箱
  ReplyTo: NULL                         # 回复地址
  KeepAlive: 30                         # 连接保持时长

mq:
  NameSrvAddr:  192.168.6.6
  NameSrvPort:  9872

2. 主要逻辑 dynamicConfig/main.go

package main

import (
	"errors"
	"flag"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"log"
	"time"
)

var (
	Conf     = &Config{} // 全局配置文件结构体
	ConfPath string      // 全局配置文件的路径
)

type Config struct {
	System struct {
		Addr  string
		Mode  string
		Debug bool
	}
	Database struct {
		Name            string
		Addr            string
		UserName        string
		Password        string
		ShowLog         bool
		MaxIdleConn     int
		MaxOpenConn     int
		ConnMaxLifeTime time.Duration
	}
	Redis struct {
		Addr         string
		Password     string
		DB           int
		MinIdleConn  int
		DialTimeout  time.Duration
		ReadTimeout  time.Duration
		WriteTimeout time.Duration
		PoolSize     int
		PoolTimeout  time.Duration
	}
	LdapCfg struct {
		// 连接地址
		ConnUrl string `json:"conn_url" gorm:"type:varchar(255);unique_index;not null;comment:连接地址 逻辑外键"`
		// SSL加密方式
		SslEncryption bool `json:"ssl_encryption" gorm:"type:tinyint;length:1;comment:SSL加密方式"`
		// 超时设置
		Timeout time.Duration `json:"timeout" gorm:"type:int;comment:超时设置"`
		// 根目录
		BaseDn string `json:"base_dn" gorm:"type:varchar(255);not null;comment:根目录"`
		// 用户名
		AdminAccount string `json:"admin_account" gorm:"type:varchar(255);not null;comment:用户名"`
		// 密码
		Password string `json:"password" gorm:"type:varchar(255);not null;comment:密码"`
	}
	Email struct {
		Host      string
		Port      string
		Username  string
		Password  string
		NickName  string
		Address   string
		ReplyTo   string
		KeepAlive int
	}
	Mq struct {
		NameSrvAddr string
		NameSrvPort string
	}
}

// Init 初始化配置
func Init(path string) (*Config, error) {
	cfgFile, err := LoadConfig(path)
	if err != nil {
		log.Fatalf("LoadConfig: %v", err)
	}
	Conf, err = ParseConfig(cfgFile)
	if err != nil {
		log.Fatalf("ParseConfig: %v", err)
	}

	return Conf, nil
}

// LoadConfig 加载配置文件
func LoadConfig(path string) (*viper.Viper, error) {
	v := viper.New()
	if path != "" {
		v.SetConfigFile(path) // 如果指定了配置文件,则解析指定的配置文件
	} else {
		v.AddConfigPath("conf/")
		v.SetConfigName("conf")
	}
	v.SetConfigType("yaml")
	v.AutomaticEnv()
	if err := v.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			return nil, errors.New("config file not found")
		}
		return nil, err
	}
	go watchConfig(v)

	return v, nil
}

// ParseConfig 解析配置文件
func ParseConfig(v *viper.Viper) (c *Config, err error) {
	if err = v.Unmarshal(&c); err != nil {
		return nil, err
	}
	return c, nil
}

// watchConfig 监控文件热更新
func watchConfig(v *viper.Viper) {
	v.WatchConfig()
	v.OnConfigChange(func(event fsnotify.Event) {
		fmt.Printf("Detect conf change: %s \n", event.String())
		Conf, err := Init(ConfPath)
		if err != nil {
			fmt.Printf("Reload cfg error, %s", err)
		}
		fmt.Println("重载后的配置文件:", Conf)
	})
}

func main() {
	path := flag.String("config", "", "指定配置文件地址")
	flag.Parse()
	ConfPath = *path
	Conf, _ = Init(ConfPath)
	fmt.Println(Conf)

	// 同步阻塞 这里不像很多示例用gin
	chWait := make(chan struct{})
	<-chWait
}

测试

1. 默认配置文件

go run main.go 

然后修改配置文件 conf.yaml,可以看到获取到了更新并更新了全局配置结构体变量 Conf

go配置文件热加载_第2张图片

2. 指定配置文件路径:

go run main.go -config /Users/randolph/Downloads/test.yaml

然后修改配置文件 test.yaml,可以看到获取到了更新并更新了全局配置结构体变量 Conf

go配置文件热加载_第3张图片

总结

这里没有用 web 的方式启动,结合 gin 也是可以的,main 方法中我用无缓冲的通道做了一个同步阻塞,可以保证进程一直运行着。

Config 结构体我选用项目中比较复杂的结构体,根据需要自行改写~

你可能感兴趣的:(开发语言)