Golang如何优雅接入多个远程配置中心?_golang 读取多个apollo配置文件(4)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

下面快速介绍下xconfig包的使用与能力,然后针对包的封装实践做个讲解

获取安装
go get -u github.com/jinzaigo/xconfig

Features
  • 支持viper包诸多同名方法
  • 支持本地配置文件和远程apollo配置热加载,实时更新
  • 使用sync.RWMutex读写锁,解决了viper并发读写不安全问题
  • 支持apollo配置中心多实例配置化快速接入
接入示例
本地配置文件

指定配置文件路径完成初始化,即可通过xconfig.GetLocalIns().xxx()链式操作,读取配置

package main

import (
    "fmt"
    "github.com/jinzaigo/xconfig"
)

func main() {
    if xconfig.IsLocalLoaded() {
        fmt.Println("local config is loaded")
        return
    }
    //初始化
    configIns := xconfig.New(xconfig.WithFile("example/config.yml"))
    xconfig.InitLocalIns(configIns)

    //读取配置
    fmt.Println(xconfig.GetLocalIns().GetString("appId"))
    fmt.Println(xconfig.GetLocalIns().GetString("env"))
    fmt.Println(xconfig.GetLocalIns().GetString("apollo.one.endpoint"))
}

xxx支持的操作方法:

  • IsSet(key string) bool
  • Get(key string) interface{}
  • AllSettings() map[string]interface{}
  • GetStringMap(key string) map[string]interface{}
  • GetStringMapString(key string) map[string]string
  • GetStringSlice(key string) []string
  • GetIntSlice(key string) []int
  • GetString(key string) string
  • GetInt(key string) int
  • GetInt32(key string) int32
  • GetInt64(key string) int64
  • GetUint(key string) uint
  • GetUint32(key string) uint32
  • GetUint64(key string) uint64
  • GetFloat(key string) float64
  • GetFloat64(key string) float64
  • GetFloat32(key string) float32
  • GetBool(key string) bool
  • SubAndUnmarshal(key string, i interface{}) error
远程apollo配置中心

指定配置类型与apollo信息完成初始化,即可通过xconfig.GetRemoteIns(key).xxx()链式操作,读取配置

单实例场景

//初始化
configIns := xconfig.New(xconfig.WithConfigType("properties"))
err := configIns.AddApolloRemoteConfig(endpoint, appId, namespace, backupFile)
if err != nil {
    ...handler
}
xconfig.AddRemoteIns("ApplicationConfig", configIns)

//读取配置
fmt.Println(xconfig.GetRemoteIns("ApplicationConfig").AllSettings())

多实例场景

在本地配置文件config.yaml维护apollo配置信息,然后批量完成多个实例的初始化,即可通过xconfig.GetRemoteIns(key).xxx()链式操作,读取配置

#apollo配置,支持多实例多namespace
apollo:
  one:
    endpoint: xxx
    appId: xxx
    namespaces:
      one:
        key: ApplicationConfig   #用于读取配置,保证全局唯一,避免相互覆盖
        name: application        #注意:name不要带类型(例如application.properties),这里name和type分开配置
        type: properties
      two:
        key: cipherConfig
        name: cipher
        type: properties
    backupFile: /tmp/xconfig/apollo_bak/test.agollo #每个appId使用不同的备份文件名,避免相互覆盖

package main

import (
    "fmt"
    "github.com/jinzaigo/xconfig"
)

type ApolloConfig struct {
    Endpoint   string                     `json:"endpoint"`
    AppId      string                     `json:"appId"`
    Namespaces map[string]ApolloNameSpace `json:"namespaces"`
    BackupFile string                     `json:"backupFile"`
}

type ApolloNameSpace struct {
    Key  string `json:"key"`
    Name string `json:"name"`
    Type string `json:"type"`
}

func main() {
    //本地配置初始化
    xconfig.InitLocalIns(xconfig.New(xconfig.WithFile("example/config.yml")))
    if !xconfig.GetLocalIns().IsSet("apollo") {
        fmt.Println("without apollo key")
        return
    }

    apolloConfigs := make(map[string]ApolloConfig, 0)
    err := xconfig.GetLocalIns().SubAndUnmarshal("apollo", &apolloConfigs)
    if err != nil {
        fmt.Println(apolloConfigs)
        fmt.Println("SubAndUnmarshal error:", err.Error())
        return
    }

    //多实例初始化
    for _, apolloConfig := range apolloConfigs {
        for _, namespaceConf := range apolloConfig.Namespaces {
            configIns := xconfig.New(xconfig.WithConfigType(namespaceConf.Type))
            err = configIns.AddApolloRemoteConfig(apolloConfig.Endpoint, apolloConfig.AppId, namespaceConf.Name, apolloConfig.BackupFile)
            if err != nil {
                fmt.Println("AddApolloRemoteConfig error:" + err.Error())
            }
            xconfig.AddRemoteIns(namespaceConf.Key, configIns)
        }
    }

    //读取
    fmt.Println(xconfig.GetRemoteIns("ApplicationConfig").AllSettings())
}

封装实践

欢迎大家关注我们,拥抱开源:加入我和劲仔的交流群

学会使用xconfig包后,能快速的实现本地配置文件和远程apollo配置中心多实例的接入。再进一步了解这个包在封装过程都中遇到过哪些问题,以及对应的解决方案,能更深入的理解与使用这个包,同时也有助于增加读者自己在封装新包时的实践理论基础。

1.viper远程连接不支持apollo

查看viper的使用文档,会发现viper是支持远程K/V存储连接的,所以一开始我尝试着连接apollo

v := viper.New()
v.SetConfigType("properties")
err := v.AddRemoteProvider("apollo", "http://endpoint", "application")
if err != nil {
    panic(fmt.Errorf("AddRemoteProvider error: %s", err))
}
fmt.Println("AddRemoteProvider success")
//执行结果:
//panic: AddRemoteProvider error: Unsupported Remote Provider Type "apollo"

执行后发现,并不支持apollo,随即查看viper源码,发现只支持以下3个provider

// SupportedRemoteProviders are universally supported remote providers.
var SupportedRemoteProviders = []string{"etcd", "consul", "firestore"}

解决方案:

安装shima-park/agollo包: go get -u github.com/shima-park/agollo

安装成功后,只需要在上面代码基础上,最前面加上 remote.SetAppID("appId") 即可连接成功

import (
  "fmt"
  remote "github.com/shima-park/agollo/viper-remote"
  "github.com/spf13/viper"
)

remote.SetAppID("appId")
v := viper.New()
v.SetConfigType("properties")
err := v.AddRemoteProvider("apollo", "http://endpoint", "application")
if err != nil {
    panic(fmt.Errorf("AddRemoteProvider error: %s", err))
}
fmt.Println("AddRemoteProvider success")
//执行结果:
//AddRemoteProvider success

2.agollo是怎么让viper支持apollo连接的呢

不难发现,在执行 remote.SetAppID("appId") 之前,remote.go 中init方法,会往viper.SupportedRemoteProviders中append一个"apollo",其实就是让viper认识一下这个provider,随后将viper.RemoteConfig 做重新赋值,并重新实现了viper中的Get Watch WatchChannel这3个方法,里边就会做apollo连接的适配。

//github.com/shima-park/agollo/viper-remote/remote.go 278-284行
func init() {
  viper.SupportedRemoteProviders = append(
    viper.SupportedRemoteProviders,
    "apollo",
  )
  viper.RemoteConfig = &configProvider{}
}

//github.com/spf13/viper/viper.go 113-120行
type remoteConfigFactory interface {
  Get(rp RemoteProvider) (io.Reader, error)
  Watch(rp RemoteProvider) (io.Reader, error)
  WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
}

// RemoteConfig is optional, see the remote package
var RemoteConfig remoteConfigFactory

3.agollo只支持apollo单实例,怎么扩展为多实例呢

执行remote.SetAppID("appId")之后,这个appId是往全局变量appID里写入的,并且在初始化时也是读取的这个全局变量。带来的问题就是不支持apollo多实例,那么解决呢

//github.com/shima-park/agollo/viper-remote/remote.go 26行
var (
  // apollod的appid
  appID string
  ...
)
func SetAppID(appid string) {
  appID = appid
}

//github.com/shima-park/agollo/viper-remote/remote.go 252行
switch rp.Provider() {
...
case "apollo":
    return newApolloConfigManager(appID, rp.Endpoint(), defaultAgolloOptions)
}

解决方案:

既然agollo包能让viper支持apollo连接,那么为什么我们自己的包不能让viper也支持apollo连接呢?并且我们还可以定制化的扩展成多实例连接。实现步骤如下:

  1. shima-pack/agollo/viper-remote/remote.go复制一份出来,把全局变量appID删掉
  2. 定义"providers sync.Map",实现AddProviders()方法,将多个appId往里边写入,里边带上agollo.Option相关配置;同时关键操作要将新的provider往viper.SupportedRemoteProviders append,让viper认识这个新类型
  3. 使用的地方,根据写入时用的provider 串,去读取,这样多个appId和Option就都区分开了
  4. 其他代码有标红的地方就相应改改就行了

核心代码 查看GitHub即可

//github.com/jinzaigo/xconfig/remote/remote.go
var (
  ...
  providers sync.Map
)



![img](https://img-blog.csdnimg.cn/img_convert/75b1f7297e495ec08a42c6c2cfc73a33.png)
![img](https://img-blog.csdnimg.cn/img_convert/8f5dbbe3d09c47f8bbde269f2a83d5f4.png)
![img](https://img-blog.csdnimg.cn/img_convert/174a0bfc15facba283fd5193b97774b7.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

 sync.Map
)



[外链图片转存中...(img-9tJuOc52-1715499444479)]
[外链图片转存中...(img-Ud2X78fI-1715499444480)]
[外链图片转存中...(img-IjlJC4ei-1715499444480)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值