参考:go 配置文件的热加载 - RandolphCYG的记录 - OSCHINA - 中文开源技术交流社区
原创
RandolphCYG
之前写的 gin 项目中也用到了 viper 去监控配置文件的热加载,实际上后面部署用 k8s 后,这个功能实际上没啥用了。
├── dynamicConfig
│ ├── conf
│ ├── go.mod
│ ├── go.sum
│ └── main.go
这个配置文件还可以复制一份到其他地方,例如我这边示例复制到了 /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
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
:
2. 指定配置文件路径:
go run main.go -config /Users/randolph/Downloads/test.yaml
然后修改配置文件 test.yaml
,可以看到获取到了更新并更新了全局配置结构体变量 Conf
:
这里没有用 web 的方式启动,结合 gin 也是可以的,main 方法中我用无缓冲的通道做了一个同步阻塞,可以保证进程一直运行着。
Config 结构体我选用项目中比较复杂的结构体,根据需要自行改写~