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)

        }
    }()
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang是一种强大的编程语言,非常适合构建Web应用程序。虽然有很多流行的Web框架可以帮助我们快速地建立和开发Web应用程序,但有时候可能会选择不使用框架来构建一个Golang的Web应用程序。 首先,不使用框架可以提高对Golang语言本身的了解和熟练程度。通过不使用框架,我们需要直接使用Golang的标准库来处理网络请求、路由、中间件等功能。这样可以更深入地了解Golang的并发性能、内存管理和其他底层机制,从而提高自己的编程技巧和能力。 其次,不使用框架可以更好地满足特定的需求。有时候我们可能会遇到一些特殊的需求,需要定制化的功能或性能优化。在这种情况下,使用框架可能会受到限制,而不使用框架可以让我们有更多的自由来实现自己的解决方案。 此外,不使用框架可以避免框架带来的额外复杂性和依赖性。使用框架可能需要我们学习和适应框架的规范和设计模式,也会导致我们的应用程序依赖于框架的版本和更新。而不使用框架可以减少这些额外的复杂性和依赖性,使我们的应用程序更容易维护和扩展。 当然,不使用框架也意味着我们需要更多地编写自己的代码,更多地处理底层的细节。这需要我们具备更高的技术水平和更大的耐心。但对于经验丰富的开发人员或有特定需求的项目来说,不使用框架可能是一个值得考虑的选择。 总之,不使用框架可以帮助我们提高对Golang语言本身的了解和熟练程度,满足特定的需求,减少复杂性和依赖性。但这需要我们具备更高的技术水平和耐心。在确定选择之前,需要权衡利弊并根据具体情况做出决策。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值