golang工程配置解决方案——viper框架及使用

配置解决方案——viper框架

文件读取配置

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
}

io.Reader读取

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读取

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 
写入etcd配置文件, 读取etcd 配置文件

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以及配置热更新

生产工作中,我们还是采用配置映射到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在前面已经提到,生产工作中一般有三方的程序会做这个事情。

// 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)

        }
    }()
}

你可能感兴趣的:(golang,开发语言,后端)