当前位置: 首页 > article >正文

【Viper】文件、Etcd应用配置与配置热更新,go案例

1. 应用配置

Viper 的核心功能之一是能够将配置应用到 Go 应用程序中。它支持从多种数据源加载配置,并将这些配置绑定到程序中的变量或结构体

2. 配置热更新

在某些应用场景下,我们希望 修改配置文件后,应用能够自动更新配置,而不需要重启程序。这在微服务、长时间运行的应用程序中非常有用。


1. 从文件加载配置,配置热更新

步骤代码示例说明
1. 设置配置文件viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
设置配置文件名、类型和搜索路径。
2. 读取配置文件if err := viper.ReadInConfig(); err != nil { ... }读取配置文件,如果文件不存在或格式错误会返回错误。
3. 绑定配置到变量dbHost := viper.GetString("database.host")将配置值绑定到变量。
4. 绑定配置到结构体var config Config
if err := viper.Unmarshal(&config); err != nil { ... }
将配置绑定到结构体,使用 mapstructure 标签映射字段。
5. 启用文件监听viper.WatchConfig()启用文件监听功能,支持热更新。
6. 注册回调函数viper.OnConfigChange(func(e fsnotify.Event) { ... })配置文件变化时触发回调函数。

2. 从 etcd 加载配置,配置热更新

步骤代码示例说明
1. 添加远程提供者viper.AddRemoteProvider("etcd", "http://127.0.0.1:2379", "/config/path")设置 etcd 的地址和配置路径。
2. 设置配置类型viper.SetConfigType("yaml")设置远程配置的类型(如 yaml、json 等)。
3. 读取远程配置if err := viper.ReadRemoteConfig(); err != nil { ... }从 etcd 读取配置。
4. 绑定配置到变量dbHost := viper.GetString("database.host")将配置值绑定到变量。
5. 绑定配置到结构体var config Config
if err := viper.Unmarshal(&config); err != nil { ... }
将配置绑定到结构体,使用 mapstructure 标签映射字段。
6. 启用远程监听viper.WatchRemoteConfig()启用远程配置监听功能,支持热更新。
7. 注册回调函数viper.OnRemoteConfigChange(func() { ... })远程配置变化时触发回调函数。

3. 应用配置

步骤代码示例说明
1. 绑定配置到变量dbHost := viper.GetString("database.host")直接通过键获取配置值。
2. 绑定配置到结构体var config Config
if err := viper.Unmarshal(&config); err != nil { ... }
将配置绑定到结构体,适合复杂配置。

4. 配置热更新

步骤代码示例说明
1. 文件监听热更新viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { ... })
监听本地配置文件变化,触发回调函数。
2. 远程监听热更新viper.WatchRemoteConfig()
viper.OnRemoteConfigChange(func() { ... })
监听远程配置(如 etcd)变化,触发回调函数。

  • 从文件应用
// InitConf 初始化配置函数,从指定文件路径加载配置并解析到全局变量 conf 中。
// 参数:
//   - filepath: 配置文件的路径
//   - typ: 可选参数,指定配置文件的类型(如 "yaml", "json" 等)
//
// 返回值: 无
func InitConf(filepath string, typ ...string) {
	// 创建 Viper 实例用于读取配置文件
	v := viper.New()
	v.SetConfigFile(filepath)

	// 如果指定了配置文件类型,则设置类型
	if len(typ) > 0 {
		v.SetConfigType(typ[0])
	}

	// 读取配置文件内容
	err := v.ReadInConfig()
	if err != nil {
		log.Fatalln(err) // 如果读取失败,记录错误并终止程序
	}

	// 初始化全局配置变量
	conf = &Config{}
	// 将配置文件内容解析到 conf 结构体中
	err = v.Unmarshal(conf)
	if err != nil {
		log.Fatalln(err) // 如果解析失败,记录错误并终止程序
	}

	// OnConfigChange 注册一个回调函数,当配置文件发生变化时触发。
	// 该回调函数接收一个 fsnotify.Event 类型的参数 in,表示文件系统事件。
	// 在回调函数中,使用 viper 的 Unmarshal 方法将配置文件内容解析到 conf 结构体中,
	// 并打印出 conf 的内容。
	v.OnConfigChange(func(in fsnotify.Event) {
		// 这里实践发现更改一次文件总是会连续触发两次,所以加个时间间隔
		if time.Since(lastEventTime) < time.Second {
			return
		}
		lastEventTime = time.Now()

		v.Unmarshal(conf)
		fmt.Printf("%+v\n", conf)
		fmt.Println("配置文件发生变化", in.String())
	})

	// WatchConfig 启动 viper 的配置文件监视功能,当配置文件发生变化时,
	// 会触发之前注册的 OnConfigChange 回调函数。
	v.WatchConfig() // 观察到更改,触发OnConfigChange
}

  • 从Etcd应用
// InitEtcdConf 初始化并监控从 etcd 中获取的配置。
// 该函数通过 etcd 地址、配置键和配置类型来获取远程配置,并将其解析到全局变量 etcdConf 中。
// 同时,该函数会启动一个后台 goroutine,每隔 2 秒检查一次配置是否有更新,并在配置更新时重新解析并打印配置。
//
// 参数:
//   - etcdAddr: etcd 服务器的地址。
//   - key: 在 etcd 中存储配置的键。
//   - typ: 配置的类型(如 "json", "yaml" 等)。
func InitEtcdConf(etcdAddr, key, typ string) {
	// 创建一个新的 Viper 实例,用于管理远程配置
	v := viper.New()
	// 添加 etcd3 作为远程配置提供者
	v.AddRemoteProvider("etcd3", etcdAddr, key)
	// 设置配置类型
	v.SetConfigType(typ)
	// 从远程读取配置
	err := v.ReadRemoteConfig()
	if err != nil {
		log.Fatal(err)
	}
	// 初始化全局配置变量
	etcdConf = &Config{}
	// 将远程配置解析到全局配置变量中
	err = v.Unmarshal(etcdConf)
	if err != nil {
		log.Fatal(err)
	}

	// 启动一个后台 goroutine 监控配置的更新
	go func() {
		i := 0
		for {
			// 每隔 2 秒检查一次配置更新
			<-time.After(time.Second * 2)
			// 监控远程配置的更新
			err = v.WatchRemoteConfig()
			if err != nil {
				log.Println(err)
				continue
			}
			etcdConf = &Config{}
			// 将更新后的配置重新解析到全局配置变量中
			err = v.Unmarshal(etcdConf)
			if err != nil {
				log.Println(err)
				continue
			}
			i++
			// 打印更新后的配置
			fmt.Printf("%d, %+v\n", i, etcdConf)
		}
	}()
}

main.go

// 从文件初始化配置,加载配置文件,并打印部分配置项,热更新
	config.InitConf("config.yaml")
	conf := config.GetConf()
	fmt.Printf("%+v\n", conf)
	fmt.Println(conf.Server.IP)

	// 定义 etcd 的地址、配置项的键以及本地配置文件的路径
	etcdAddr := "192.168.88.131:2379"
	key := "/0voice/viper/config.json"
	loaclFilepath := "config.json"
	writeConfToEtcd(etcdAddr, key, loaclFilepath)

	config.InitEtcdConf(etcdAddr, key, "json")
	conf = config.GetEtcdConf()
	fmt.Printf("%+v\n", conf)
	fmt.Println(conf.Server.IP)

完整go案例

config.go

package config

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

// Config 结构体用于存储配置信息,包含环境变量、服务器信息、课程列表以及其他列表项。
type Config struct {
	Env    string `mapstructure:"env"` // 环境变量,如 "dev" 或 "prod"
	Server struct {
		IP   string `mapstructure:"ip"`   // 服务器IP地址
		Port string `mapstructure:"port"` // 服务器端口号
	} `mapstructure:"server"` // 服务器相关配置
	Courses []string `mapstructure:"courses"` // 课程列表
	List    []struct {
		Name   string `mapstructure:"name"`   // 列表项名称
		Author string `mapstructure:"author"` // 列表项作者
	} `mapstructure:"list"` // 其他列表项
}

var conf *Config     // 全局配置变量
var etcdConf *Config // ETCD配置变量
var lastEventTime time.Time

// InitConf 初始化配置函数,从指定文件路径加载配置并解析到全局变量 conf 中。
// 参数:
//   - filepath: 配置文件的路径
//   - typ: 可选参数,指定配置文件的类型(如 "yaml", "json" 等)
//
// 返回值: 无
func InitConf(filepath string, typ ...string) {
	// 创建 Viper 实例用于读取配置文件
	v := viper.New()
	v.SetConfigFile(filepath)

	// 如果指定了配置文件类型,则设置类型
	if len(typ) > 0 {
		v.SetConfigType(typ[0])
	}

	// 读取配置文件内容
	err := v.ReadInConfig()
	if err != nil {
		log.Fatalln(err) // 如果读取失败,记录错误并终止程序
	}

	// 初始化全局配置变量
	conf = &Config{}
	// 将配置文件内容解析到 conf 结构体中
	err = v.Unmarshal(conf)
	if err != nil {
		log.Fatalln(err) // 如果解析失败,记录错误并终止程序
	}

	// OnConfigChange 注册一个回调函数,当配置文件发生变化时触发。
	// 该回调函数接收一个 fsnotify.Event 类型的参数 in,表示文件系统事件。
	// 在回调函数中,使用 viper 的 Unmarshal 方法将配置文件内容解析到 conf 结构体中,
	// 并打印出 conf 的内容。
	v.OnConfigChange(func(in fsnotify.Event) {
		// 这里实践发现更改一次文件总是会连续触发两次,所以加个时间间隔
		if time.Since(lastEventTime) < time.Second {
			return
		}
		lastEventTime = time.Now()

		v.Unmarshal(conf)
		fmt.Printf("%+v\n", conf)
		fmt.Println("配置文件发生变化", in.String())
	})

	// WatchConfig 启动 viper 的配置文件监视功能,当配置文件发生变化时,
	// 会触发之前注册的 OnConfigChange 回调函数。
	v.WatchConfig() // 观察到更改,触发OnConfigChange
}

func GetConf() *Config {
	return conf
}

// InitEtcdConf 初始化并监控从 etcd 中获取的配置。
// 该函数通过 etcd 地址、配置键和配置类型来获取远程配置,并将其解析到全局变量 etcdConf 中。
// 同时,该函数会启动一个后台 goroutine,每隔 2 秒检查一次配置是否有更新,并在配置更新时重新解析并打印配置。
//
// 参数:
//   - etcdAddr: etcd 服务器的地址。
//   - key: 在 etcd 中存储配置的键。
//   - typ: 配置的类型(如 "json", "yaml" 等)。
func InitEtcdConf(etcdAddr, key, typ string) {
	// 创建一个新的 Viper 实例,用于管理远程配置
	v := viper.New()
	// 添加 etcd3 作为远程配置提供者
	v.AddRemoteProvider("etcd3", etcdAddr, key)
	// 设置配置类型
	v.SetConfigType(typ)
	// 从远程读取配置
	err := v.ReadRemoteConfig()
	if err != nil {
		log.Fatal(err)
	}
	// 初始化全局配置变量
	etcdConf = &Config{}
	// 将远程配置解析到全局配置变量中
	err = v.Unmarshal(etcdConf)
	if err != nil {
		log.Fatal(err)
	}

	// 启动一个后台 goroutine 监控配置的更新
	go func() {
		i := 0
		for {
			// 每隔 2 秒检查一次配置更新
			<-time.After(time.Second * 2)
			// 监控远程配置的更新
			err = v.WatchRemoteConfig()
			if err != nil {
				log.Println(err)
				continue
			}
			etcdConf = &Config{}
			// 将更新后的配置重新解析到全局配置变量中
			err = v.Unmarshal(etcdConf)
			if err != nil {
				log.Println(err)
				continue
			}
			i++
			// 打印更新后的配置
			fmt.Printf("%d, %+v\n", i, etcdConf)
		}
	}()
}

func GetEtcdConf() *Config {
	return etcdConf
}

main.go

package main

import (
	"bytes"
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"golang20-viper/config"
	"log"
	"os"
	"os/signal"
)

func main() {
	//loadFile()
	//loadEnv()
	//loadReader()
	//loadEtcd()

	// 从文件初始化配置,加载配置文件,并打印部分配置项,热更新
	//config.InitConf("config.yaml")
	//conf := config.GetConf()
	//fmt.Printf("%+v\n", conf)
	//fmt.Println(conf.Server.IP)

	// 定义 etcd 的地址、配置项的键以及本地配置文件的路径
	etcdAddr := "192.168.88.131:2379"
	key := "/0voice/viper/config.json"
	loaclFilepath := "config.json"
	writeConfToEtcd(etcdAddr, key, loaclFilepath)

	config.InitEtcdConf(etcdAddr, key, "json")
	conf := config.GetEtcdConf()
	fmt.Printf("%+v\n", conf)
	fmt.Println(conf.Server.IP)

	// 创建一个上下文(context)对象,并将其与信号通知机制绑定。
	// 当接收到 os.Interrupt 或 os.Kill 信号时,上下文将被取消。
	// 使用 defer 确保在函数退出时停止信号监听,释放资源。
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
	defer stop()

	// 等待上下文被取消,通常是在接收到信号后。
	// 这行代码会阻塞,直到 ctx.Done() 通道被关闭。
	<-ctx.Done()
}

// loadFile 函数演示了如何从不同格式的配置文件中加载配置。
// 它依次尝试加载 .env, .json, .yaml, .toml 和 .yaml 文件,并打印出特定配置项的值。
func loadFile() {
	// 从 "config.env" 文件加载配置,并打印出环境、服务器端口和课程信息。
	v1, err := config.LoadFromFile("config.env", "env")
	fmt.Println("config.env", err, v1.Get("env"), v1.Get("server.port"), v1.Get("courses"))

	// 从 "config.json" 文件加载配置,包括环境、服务器端口、课程信息和作者信息。
	v2, err := config.LoadFromFile("config.json")
	fmt.Println("config.json", err, v2.Get("env"), v2.Get("server.port"), v2.Get("courses").([]any)[0], v2.Get("list").([]any)[0].(map[string]any)["author"])

	// 从 "config.noext" 文件加载 YAML 格式的配置,同样打印环境、服务器端口、课程信息和作者信息。
	v3, err := config.LoadFromFile("config.noext", "yaml")
	fmt.Println("config.noext", err, v3.Get("env"), v3.Get("server.port"), v3.Get("courses").([]any)[0], v3.Get("list").([]any)[0].(map[string]any)["author"])

	// 从 "config.toml" 文件加载配置,展示如何获取嵌套配置项的值。
	v4, err := config.LoadFromFile("config.toml")
	fmt.Println("config.toml", err, v4.Get("env"), v4.Get("server.port"), v4.Get("courses").(map[string]any)["list"].([]any)[0], v4.Get("list").([]any)[0].(map[string]any)["author"])

	// 从 "config.yaml" 文件加载配置,再次打印出环境、服务器端口、课程信息和作者信息,验证不同格式文件的兼容性。
	v5, err := config.LoadFromFile("config.yaml")
	fmt.Println("config.yaml", err, v5.Get("env"), v5.Get("server.port"), v5.Get("courses").([]any)[0], v5.Get("list").([]any)[0].(map[string]any)["author"])
}

func loadEnv() {
	v, err := config.LoadFromEnv()
	fmt.Println(err, v.Get("GOROOT"), v.Get("gopath"))
}

// loadReader 读取配置文件并解析特定配置项。
// 该函数没有输入参数和返回值。
// 功能描述:
// 1. 读取名为 "config.yaml" 的配置文件。
// 2. 如果读取过程中遇到错误,记录错误信息并终止程序运行。
// 3. 使用 bytes.NewReader 创建一个字节流读取器来读取配置文件内容。
// 4. 调用 config.LoadFromIoReader 函数从字节流读取器中加载配置信息。
// 5. 打印解析后的配置项,包括环境变量、服务器端口、课程信息和作者信息。
func loadReader() {
	// 读取配置文件 "config.yaml" 的内容到 byteList。
	byteList, err := os.ReadFile("config.yaml")
	if err != nil {
		// 如果读取配置文件时发生错误,记录错误信息并终止程序。
		log.Fatalln(err)
	}
	// 创建一个新的字节流读取器来读取配置文件内容。
	r := bytes.NewReader(byteList)
	// 从字节流读取器中加载配置信息,并处理可能的错误。
	v, err := config.LoadFromIoReader(r, "yaml")
	// 打印解析后的配置项。
	fmt.Println("io.reader", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]any)[0], v.Get("list").([]any)[0].(map[string]any)["author"])
}

// loadEtcd 函数用于从本地文件加载配置并写入到 etcd 中,然后从 etcd 中读取配置并打印部分配置项。
// 该函数不接收任何参数,也不返回任何值。
func loadEtcd() {
	// 定义 etcd 的地址、配置项的键以及本地配置文件的路径
	etcdAddr := "192.168.88.131:2379"
	key := "/0voice/viper/config.yaml"
	loaclFilepath := "config.yaml"

	// 将本地配置文件的内容写入到 etcd 中
	writeConfToEtcd(etcdAddr, key, loaclFilepath)

	// 从 etcd 中加载配置,并解析为 yaml 格式
	v, err := config.LoadFromEtcd(etcdAddr, key, "yaml")

	// 打印从 etcd 中读取的配置项,包括环境、服务器端口、课程列表中的第一个课程以及列表中的第一个作者的名称
	fmt.Println("etcd", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]any)[0], v.Get("list").([]any)[0].(map[string]any)["author"])
	//fmt.Println(err, v)
}

// writeConfToEtcd 将本地配置文件内容写入到etcd指定键中
// 参数说明:
//   - etcdAddr: etcd服务地址,格式为"IP:PORT"
//   - key: etcd中存储配置的键名
//   - localFilepath: 本地配置文件的路径
//
// 功能说明:
//
//	读取配置文件,将其内容存入etcd集群的指定键中
//	遇到任何错误(文件读取、连接etcd、写入etcd)将直接终止程序
func writeConfToEtcd(etcdAddr, key, localFilepath string) {
	// 从固定路径读取配置文件内容
	byteList, err := os.ReadFile(localFilepath)
	if err != nil {
		// 如果读取配置文件时发生错误,记录错误信息并终止程序。
		log.Fatalln(err)
	}
	v := string(byteList)

	// 创建etcd客户端连接
	cli, err := clientv3.New(clientv3.Config{
		Endpoints: []string{etcdAddr},
	})
	if err != nil {
		log.Fatal(err)
	}

	// 将配置内容写入etcd指定键
	_, err = cli.Put(context.Background(), key, v)
	if err != nil {
		log.Fatal(err)
	}
}


https://github.com/0voice


http://www.kler.cn/a/552157.html

相关文章:

  • 排序与算法:选择排序
  • C++入门小清单
  • 【Spring】Spring配置文件
  • 利用Java爬虫精准获取淘宝商品描述:实战案例指南
  • .NET 9.0 的 Blazor Web App 项目,Bootstrap Blazor 全局异常 <ErrorLogger> 使用备忘
  • 【C语言】C语言 食堂自动化管理系统(源码+数据文件)【独一无二】
  • ubuntu22.04离线安装nginx
  • 【转】“小前台,大中台”战略—以阿里云中台设计为例
  • 【Spring详解二】容器的基本实现
  • 国产网络变压器有哪些品牌比较好
  • Upload-labs
  • 从 0 到 1:Spring Boot 构建高效应用指南
  • java断点调试(debug)
  • 数据结构--双向链表,双向循环链表
  • Redis7——基础篇(四)
  • 深度学习06 寻找与保存最优模型
  • Flink SQL与Doris实时数仓Join实战教程(理论+实例保姆级教程)
  • WPS/WORD$OffterAI
  • Vue3项目,蛋糕商城系统
  • C++ Primer 访问控制与封装