方案1:直接使用etcd

代码示例:

func main(){
    key := "etcd_data/test2" // 服务配置 键名

    res,err := cli.Get(context.Background(), key)
    if err != nil {
        fmt.Println("获取数据出错, err:", err)
        return
    }
    // 循环打印出值
    for _, ev := range res.Kvs {
        // 此处用于演示,仅打印查看,实际这里是获取配置后,解析并放入全局变量中
        fmt.Printf("%s:%s\n", ev.Key, ev.Value)
    }

    // 监视 key 的数据变动,返回一个只读通道 // <-chan WatchResponse
    // watch key:address change 监控etcd中key的变化-创建、更改、删除
    rch := cli.Watch(context.Background(), key)
    for wresp := range rch {
        // 读取 WatchResponse 响应
        // 检查事件变化
        for _, ev := range wresp.Events {
            // 输出变化后数据,以及变化类型,这里用于演示,仅输出变化
            fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
            // 实际应用时,此处做全局配置变量的变化解析,并进行热重载
        }
    }
}

方案2:viper + etcd(或其他开源配置中心)

注意:需引入 go get github.com/spf13/viper/remote

func main(){
    // viper 读取本地文件配置
    //configByFile(viper.New())

    // viper 读取 etcd 配置
    configByEtcd(viper.New())

    select{}
}

func configByEtcd(vp *viper.Viper){
    var err error
    key := "etcd_data/test2"
    if err = vp.AddRemoteProvider("etcd3", "http://127.0.0.1:2379", key); err != nil {
        log.Fatal(err)
    }

    vp.SetConfigType("json")
    if err = vp.ReadRemoteConfig();err != nil {
        log.Fatal(err)
    }

    conf := make(map[string]interface{})
    if err = vp.Unmarshal(&conf);err != nil {
        log.Fatal(err)
    }

    fmt.Println("获取的配置信息 ",conf)

    //fmt.Println(vp.Get("addr"))
    //fmt.Println(vp.Get("port"))

    // 循环调用监听远程配置
    go watchRemoteConfig(vp, conf)

    // 或者使用管道监听
    //err = vp.WatchRemoteConfigOnChannel()
    //if err != nil {
    //    log.Fatal("unable to read remote config: ", err)
    //}
    //if err = vp.Unmarshal(&conf); nil != err{
    //    log.Fatal(err)
    //}
    //fmt.Println("获取的配置信息 ",conf)
}

func watchRemoteConfig(vp *viper.Viper, conf map[string]interface{}){
    for {
        // 循环调用,每隔5秒调用一次
        time.Sleep(5 * time.Second) // delay after each request

        // 监听远程配置,其实这个也是调用读取配置的接口,不是实际意义上的监听,需要循环调用
        // 可以使用 vp.WatchRemoteConfigOnChannel() 代替 循环调用,备注 vp.WatchRemoteConfigOnChannel() 会出现并发不安全的问题
        err := vp.WatchRemoteConfig()
        if err != nil {
            log.Printf("unable to read remote config: %v", err)
            continue
        }

        // unmarshal new config into our runtime config struct. you can also use channel
        // to implement a signal to notify the system of the changes
        // 将新配置解组到我们的运行时配置结构中。
        // 您还可以使用通道实现信号以通知系统更改
        if err = vp.Unmarshal(&conf); nil != err{
            log.Printf("unable to read remote config: %v", err)
            continue
        }
        fmt.Println("获取的配置信息 ",conf)
    }
}


// 使用 viper 从文件中读取配置
func configByFile(vp *viper.Viper){
    var err error
    vp.SetConfigName("app")

    vp.SetConfigType("json")

    vp.AddConfigPath("./config/")
    // 读取文件
    if err = vp.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found; ignore error if desired
            log.Fatal("找不到配置文件")
        } else {
            // Config file was found but another error was produced
            log.Fatal("其他未知错误")
        }
    }

    conf := make(map[string]interface{})
    if err = vp.Unmarshal(&conf); err != nil {
        log.Fatal("Unmarshal ",err)
    }

    fmt.Println("从本地读取 conf: ", conf)

    // watch
    go func() {
        vp.OnConfigChange(func(e fsnotify.Event) {
            fmt.Println("Config file changed:", e.Name)
            fmt.Println(e.String())
            fmt.Println(e.Op.String())
        })
        vp.WatchConfig()
    }()
}
作者:joker.liu  创建时间:2023-05-30 17:24
最后编辑:joker.liu  更新时间:2023-05-30 17:35