package config
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io"
"log"
)
// 从本地文件读取配置
func LoadFromFile(filepath string, typ ...string) (*viper.Viper, error) {
v := viper.New()
v.SetConfigFile(filepath)
if len(typ) > 0 {
v.SetConfigType(typ[0])
}
err := v.ReadInConfig()
return v, err
}
package config
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io"
"log"
)
func LoadFromEnv() (*viper.Viper, error) {
v := viper.New()
//自动绑定环境变量
//v.AutomaticEnv()
//指定绑定的环境变量
v.BindEnv("GOPATH")
v.BindEnv("GOROOT")
return v, nil
}
package config
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io"
"log"
)
func LoadFromIoReader(reader io.Reader, typ string) (*viper.Viper, error) {
v := viper.New()
v.SetConfigType(typ)
err := v.ReadConfig(reader)
return v, err
}
etcd单节点集群运行
docker run -d \
-p 2379:2379 \
-p 2380:2380 \
--restart always \
--privileged \
--volume=/home/etcd:/etcd-data \
--name etcd quay.io/coreos/etcd:v3.5.7 \
/usr/local/bin/etcd \
--data-dir=/etcd-data --name node1 \
--initial-advertise-peer-urls http://10.74.18.61:2380 \
--listen-peer-urls http://0.0.0.0:2380 \
--advertise-client-urls http://10.74.18.61:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--initial-cluster node1=http://10.74.18.61:2380 \
--initial-cluster-token tkn \
--initial-cluster-state new
viper 读取etcd 配置
config.go
package config
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io"
"log"
)
func LoadFromEtcd(etcdAddr, key, typ string) (*viper.Viper, error) {
v := viper.New()
// viper读取etcd中的一个key
err := v.AddRemoteProvider("etcd3", etcdAddr, key)
if err != nil {
log.Println(err)
return nil, err
}
v.SetConfigType(typ)
err = v.ReadRemoteConfig()
return v, err
}
package main
import (
"bytes"
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
"log"
"os"
_ "os/signal"
"viper-practice/config"
)
func loadEtcd() {
//向etcd写入数据
etcdAddr := "10.74.18.61:2379"
key := "/test/viper/config.yaml"
localFilepath := "config.yaml"
writeConfToEtcd(etcdAddr, key, localFilepath)
//从etcd加载配置,etcd充当了配置中心
v, err := config.LoadFromEtcd(etcdAddr, key, "yaml")
fmt.Println("etcd", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]interface{})[0], v.Get("list").([]interface{})[0].(map[string]interface{})["author"])
}
func writeConfToEtcd(etcdAddr, key, localFilepath string) {
byteList, err := os.ReadFile(localFilepath)
if err != nil {
log.Fatal(err)
}
value := string(byteList)
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{etcdAddr},
})
if err != nil {
log.Fatal(err)
}
_, err = cli.Put(context.Background(), key, value)
if err != nil {
log.Fatal(err)
}
}
func main() {
//loadFile()
//loadEnv()
//loadReader()
loadEtcd()
}
docker etcd查询
docker exec etcd etcdctl get [key]
source.go
package config
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
"io"
"log"
)
// LoadFromFile 从本地文件读取配置
func LoadFromFile(filepath string, typ ...string) (*viper.Viper, error) {
v := viper.New()
v.SetConfigFile(filepath)
if len(typ) > 0 {
v.SetConfigType(typ[0])
}
err := v.ReadInConfig()
return v, err
}
func LoadFromEnv() (*viper.Viper, error) {
v := viper.New()
//自动绑定环境变量
//v.AutomaticEnv()
//指定绑定的环境变量
v.BindEnv("GOPATH")
v.BindEnv("GOROOT")
return v, nil
}
func LoadFromIoReader(reader io.Reader, typ string) (*viper.Viper, error) {
v := viper.New()
v.SetConfigType(typ)
err := v.ReadConfig(reader)
return v, err
}
func LoadFromEtcd(etcdAddr, key, typ string) (*viper.Viper, error) {
v := viper.New()
// viper读取etcd中的一个key
err := v.AddRemoteProvider("etcd3", etcdAddr, key)
if err != nil {
log.Println(err)
return nil, err
}
v.SetConfigType(typ)
err = v.ReadRemoteConfig()
return v, err
}
main.go
package main
import (
"bytes"
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
"log"
"os"
_ "os/signal"
"viper-practice/config"
)
func main() {
//loadFile()
//loadEnv()
//loadReader()
loadEtcd()
}
func loadFile() {
// 获取viper对象,打印对应配置
// viper对象里以键值对方式存储
// 环境变量的值都是字符串处理,没有复杂的对象存储
v1, err := config.LoadFromFile("config.env")
fmt.Println("config.env", err, v1.Get("env"), v1.Get("server.port"), v1.Get("courses"))
v2, err := config.LoadFromFile("config.json")
fmt.Println("config.json", err, v2.Get("env"), v2.Get("server.port"), v2.Get("courses").([]interface{})[0], v2.Get("list").([]interface{})[0].(map[string]interface{})["author"])
// 识别不了的格式,可以指定格式读取
v3, err := config.LoadFromFile("config.noext", "yaml")
fmt.Println("config.noext", err, v3.Get("env"), v3.Get("server.port"), v3.Get("courses").([]interface{})[0], v3.Get("list").([]interface{})[0].(map[string]interface{})["author"])
v4, err := config.LoadFromFile("config.toml")
fmt.Println("config.toml", err, v4.Get("env"), v4.Get("server.port"), v4.Get("courses").(map[string]interface{})["list"].([]interface{})[0], v4.Get("list").([]interface{})[0].(map[string]interface{})["author"])
v5, err := config.LoadFromFile("config.yaml")
fmt.Println("config.yaml", err, v5.Get("env"), v5.Get("server.port"), v5.Get("courses").([]interface{})[0], v5.Get("list").([]interface{})[0].(map[string]interface{})["author"])
// 这种只是测试一下viper, 不推荐这么干, 一般是定义结构去读取配置文件比较方便
}
func loadEnv() {
v, err := config.LoadFromEnv()
fmt.Println(err, v.Get("GOROOT"), v.Get("GOPATH"))
}
func loadReader() {
byteList, err := os.ReadFile("config.yaml")
if err != nil {
log.Fatal(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").([]interface{})[0], v.Get("list").([]interface{})[0].(map[string]interface{})["author"])
}
func loadEtcd() {
//向etcd写入数据
etcdAddr := "10.74.18.61:2379"
key := "/test/viper/config.yaml"
localFilepath := "config.yaml"
writeConfToEtcd(etcdAddr, key, localFilepath)
//从etcd加载配置,etcd充当了配置中心
v, err := config.LoadFromEtcd(etcdAddr, key, "yaml")
fmt.Println("etcd", err, v.Get("env"), v.Get("server.port"), v.Get("courses").([]interface{})[0], v.Get("list").([]interface{})[0].(map[string]interface{})["author"])
}
func writeConfToEtcd(etcdAddr, key, localFilepath string) {
byteList, err := os.ReadFile(localFilepath)
if err != nil {
log.Fatal(err)
}
value := string(byteList)
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{etcdAddr},
})
if err != nil {
log.Fatal(err)
}
_, err = cli.Put(context.Background(), key, value)
if err != nil {
log.Fatal(err)
}
}
生产工作中,我们还是采用配置映射到struct的方式。同时在配置变化的时候,希望可以做到配置的热更新,不用重新启动程序
下面是一个配置文件结构定义,viper有预定义的标签tag mapstructure
来标识配置项
package config
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"log"
"time"
)
// mapstructure 是viper特有的tag
type Config struct {
Env string `mapstructure:"env"`
Server struct {
IP string `mapstructure:"ip"`
Port string `mapstructure:"port"`
}
Courses []string `mapstructure:"courses"`
List []struct {
Name string `mapstructure:"name"`
Author string `mapstructure:"author"`
} `mapstructure:"list"`
}
var conf *Config
var etcdConf *Config
func InitConf(filepath string, typ ...string) {
v := viper.New()
v.SetConfigFile(filepath)
if len(typ) > 0 {
v.SetConfigType(typ[0])
}
err := v.ReadInConfig()
if err != nil {
log.Fatal(err)
}
conf = &Config{}
err = v.Unmarshal(conf)
if err != nil {
log.Fatal(err)
}
v.OnConfigChange(func(in fsnotify.Event) {
// 重新加载配置文件到对象
v.Unmarshal(conf)
// 打印新的配置信息,验证结果
fmt.Printf("%+v\n", conf)
})
v.WatchConfig()
}
如何将配置写入etcd在前面已经提到,生产工作中一般有三方的程序会做这个事情。
// etcd配置热更新
func InitEtcdConf(etcdAddr, key, typ string) {
v := viper.New()
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)
}
go func() {
i := 0
for {
<-time.After(time.Second * 3)
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)
}
}()
}