欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心

 更新時(shí)間:2023年05月29日 14:29:23   作者:王中陽Go  
這篇文章主要為大家為大家介紹了Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

本文基于viper實(shí)現(xiàn)了apollo多實(shí)例快速接入,授人以漁,帶著大家讀源碼,詳解實(shí)現(xiàn)思路,封裝成自己的工具類并且開源。

viper是適用于go應(yīng)用程序的配置解決方案,這款配置管理神器,支持多種類型、開箱即用、極易上手。

本地配置文件的接入能很快速的完成,那么對于遠(yuǎn)程apollo配置中心的接入,是否也能很快速完成呢?如果有多個(gè)apollo實(shí)例都需要接入,是否能支持呢?以及apollo遠(yuǎn)程配置變更后,是否能支持熱加載,實(shí)時(shí)更新呢?

擁抱開源

帶著上面的這些問題,結(jié)合實(shí)際商業(yè)項(xiàng)目的實(shí)踐,已經(jīng)有較成熟的解決方案。本著分享的原則,現(xiàn)已將xconfig包脫敏開源:github地址,歡迎體驗(yàn)和star。

下面快速介紹下xconfig包的使用與能力,然后針對包的封裝實(shí)踐做個(gè)講解

獲取安裝

go get -u github.com/jinzaigo/xconfig

Features

  • 支持viper包諸多同名方法
  • 支持本地配置文件和遠(yuǎn)程apollo配置熱加載,實(shí)時(shí)更新
  • 使用sync.RWMutex讀寫鎖,解決了viper并發(fā)讀寫不安全問題
  • 支持apollo配置中心多實(shí)例配置化快速接入

接入示例

本地配置文件

指定配置文件路徑完成初始化,即可通過xconfig.GetLocalIns().xxx()鏈?zhǔn)讲僮鳎x取配置

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

遠(yuǎn)程apollo配置中心

指定配置類型與apollo信息完成初始化,即可通過xconfig.GetRemoteIns(key).xxx()鏈?zhǔn)讲僮?,讀取配置

單實(shí)例場景

//初始化
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())

多實(shí)例場景

在本地配置文件config.yaml維護(hù)apollo配置信息,然后批量完成多個(gè)實(shí)例的初始化,即可通過xconfig.GetRemoteIns(key).xxx()鏈?zhǔn)讲僮鳎x取配置

#apollo配置,支持多實(shí)例多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 #每個(gè)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
    }
    //多實(shí)例初始化
    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())
}

封裝實(shí)踐

學(xué)會使用xconfig包后,能快速的實(shí)現(xiàn)本地配置文件和遠(yuǎn)程apollo配置中心多實(shí)例的接入。再進(jìn)一步了解這個(gè)包在封裝過程都中遇到過哪些問題,以及對應(yīng)的解決方案,能更深入的理解與使用這個(gè)包,同時(shí)也有助于增加讀者自己在封裝新包時(shí)的實(shí)踐理論基礎(chǔ)。

1.viper遠(yuǎn)程連接不支持apollo

查看viper的使用文檔,會發(fā)現(xiàn)viper是支持遠(yuǎn)程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")
//執(zhí)行結(jié)果:
//panic: AddRemoteProvider error: Unsupported Remote Provider Type "apollo"

執(zhí)行后發(fā)現(xiàn),并不支持apollo,隨即查看viper源碼,發(fā)現(xiàn)只支持以下3個(gè)provider

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

解決方案:

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

安裝成功后,只需要在上面代碼基礎(chǔ)上,最前面加上 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")
//執(zhí)行結(jié)果:
//AddRemoteProvider success

2.agollo是怎么讓viper支持apollo連接的呢

不難發(fā)現(xiàn),在執(zhí)行 remote.SetAppID("appId") 之前,remote.go 中init方法,會往viper.SupportedRemoteProviders中append一個(gè)"apollo",其實(shí)就是讓viper認(rèn)識一下這個(gè)provider,隨后將viper.RemoteConfig 做重新賦值,并重新實(shí)現(xiàn)了viper中的Get Watch WatchChannel這3個(gè)方法,里邊就會做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單實(shí)例,怎么擴(kuò)展為多實(shí)例呢

執(zhí)行remote.SetAppID("appId")之后,這個(gè)appId是往全局變量appID里寫入的,并且在初始化時(shí)也是讀取的這個(gè)全局變量。帶來的問題就是不支持apollo多實(shí)例,那么解決呢

//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連接呢?并且我們還可以定制化的擴(kuò)展成多實(shí)例連接。實(shí)現(xiàn)步驟如下:

  • shima-pack/agollo/viper-remote/remote.go復(fù)制一份出來,把全局變量appID刪掉
  • 定義"providers sync.Map",實(shí)現(xiàn)AddProviders()方法,將多個(gè)appId往里邊寫入,里邊帶上agollo.Option相關(guān)配置;同時(shí)關(guān)鍵操作要將新的provider往viper.SupportedRemoteProviders append,讓viper認(rèn)識這個(gè)新類型
  • 使用的地方,根據(jù)寫入時(shí)用的provider 串,去讀取,這樣多個(gè)appId和Option就都區(qū)分開了
  • 其他代碼有標(biāo)紅的地方就相應(yīng)改改就行了

核心代碼 查看GitHub即可

//github.com/jinzaigo/xconfig/remote/remote.go
var (
  ...
  providers sync.Map
)
func init() {
  viper.RemoteConfig = &configProvider{} //目的:重寫viper.RemoteConfig的相關(guān)方法
}
type conf struct {
  appId string
  opts  []agollo.Option
}
//【重要】這里是實(shí)現(xiàn)支持多個(gè)appId的核心操作
func AddProviders(appId string, opts ...agollo.Option) string {
    provider := "apollo:" + appId
    _, loaded := providers.LoadOrStore(provider, conf{
        appId: appId,
        opts:  opts,
    })
    //之前未存儲過,則向viper新增一個(gè)provider,讓viper認(rèn)識這個(gè)新提供器
    if !loaded {
        viper.SupportedRemoteProviders = append(
            viper.SupportedRemoteProviders,
            provider,
        )
    }
    return provider
}
//使用的地方
func newApolloConfigManager(rp viper.RemoteProvider) (*apolloConfigManager, error) {
  //讀取provider相關(guān)配置
  providerConf, ok := providers.Load(rp.Provider())
  if !ok {
    return nil, ErrUnsupportedProvider
  }
  p := providerConf.(conf)
  if p.appId == "" {
    return nil, errors.New("The appid is not set")
  }
  ...
}

4.viper開啟熱加載后會有并發(fā)讀寫不安全問題

首先 viper的使用文檔,也說明了這個(gè)并發(fā)讀寫不安全問題,建議使用sync包避免panic

然后本地通過-race試驗(yàn),也發(fā)現(xiàn)會有這個(gè)競態(tài)問題

進(jìn)一步分析viper實(shí)現(xiàn)熱加載的源代碼:其實(shí)是通過協(xié)程實(shí)時(shí)更新kvstrore這個(gè)map,讀取數(shù)據(jù)的時(shí)候也是從kvstore讀取,并沒有加鎖,所以會有并發(fā)讀寫不安全問題

// 在github.com/spf13/viper/viper.go 1909行
// Retrieve the first found remote configuration.
func (v *Viper) watchKeyValueConfigOnChannel() error {
  if len(v.remoteProviders) == 0 {
    return RemoteConfigError("No Remote Providers")
  }
  for _, rp := range v.remoteProviders {
    respc, _ := RemoteConfig.WatchChannel(rp)
    // Todo: Add quit channel
    go func(rc <-chan *RemoteResponse) {
      for {
        b := <-rc
        reader := bytes.NewReader(b.Value)
        v.unmarshalReader(reader, v.kvstore)
      }
    }(respc)
    return nil
  }
  return RemoteConfigError("No Files Found")
}

解決方案:

寫:不使用viper自帶熱加載方法,而是采用重寫,也是使用協(xié)程實(shí)時(shí)更新,但會加讀寫鎖。

讀:也加讀寫鎖

讀寫鎖核心代碼GitHub

//github.com/jinzaigo/xconfig/config.go
type Config struct {
    configType string
    viper      *viper.Viper
    viperLock  sync.RWMutex
}
//寫
//_ = c.viper.WatchRemoteConfigOnChannel()
respc, _ := viper.RemoteConfig.WatchChannel(remote.NewProviderSt(provider, endpoint, namespace, ""))
go func(rc <-chan *viper.RemoteResponse) {
    for {
        <-rc
        c.viperLock.Lock()
        err = c.viper.ReadRemoteConfig()
        c.viperLock.Unlock()
    }
}(respc)
//讀
func (c *Config) Get(key string) interface{} {
    c.viperLock.RLock()
    defer c.viperLock.RUnlock()
    return c.viper.Get(key)
}

5.如何正確的輸入namespace參數(shù)

問題描述:

調(diào)用agollo包中的相關(guān)方法,輸入namespace=application.properties(帶類型),發(fā)現(xiàn)主動拉取數(shù)據(jù)成功,遠(yuǎn)程變更通知后數(shù)據(jù)拉取失??;輸入namespace=application(不帶類型),發(fā)現(xiàn)主動拉取數(shù)據(jù)成功,遠(yuǎn)程變更通知后數(shù)據(jù)拉取也能成功。兩者輸入差異就在于是否帶類型

問題原因:

查看Apollo官方接口文檔,配置更新推送接口notifications/v2 notifications字段說明,一目了然。

基于上述說明,我們在代碼里做了兼容處理,并且配置文件也加上了使用說明

//github.com/jinzaigo/xconfig/config.go 72行
func (c *Config) AddApolloRemoteConfig(endpoint, appId, namespace, backupFile string) error {
    ...
    //namespace默認(rèn)類型不用加后綴,非默認(rèn)類型需要加后綴(備注:這里會涉及到apollo變更通知后的熱加載操作 Start->longPoll)
    if c.configType != "properties" {
        namespace = namespace + "." + c.configType
    }
    ...
}
//config.yml配置說明
namespaces:
    one:
        key: ApplicationConfig   #用于讀取配置,保證全局唯一,避免相互覆蓋
        name: application        #注意:name不要帶類型(例如application.properties),這里name和type分開配置
        type: properties

總結(jié)

基于實(shí)際商業(yè)項(xiàng)目實(shí)踐,提升配置管理組件能力,實(shí)現(xiàn)了本地配置文件與遠(yuǎn)程apollo配置中心多實(shí)例快速接入;

從xconfig包的快速上手的使用說明到封裝實(shí)踐難點(diǎn)痛點(diǎn)的解析,雙管齊下,讓你更深入的理解,希望對大家有幫助。

以上就是詳解Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心的詳細(xì)內(nèi)容,更多關(guān)于Golang接入遠(yuǎn)程配置中心的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言實(shí)現(xiàn)分布式鎖

    Go語言實(shí)現(xiàn)分布式鎖

    分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問這些資源時(shí),需要通過一些互斥手段來防止彼此之間的干擾以保證一致性,在這種情況下,就需要使用分布式鎖了
    2023-01-01
  • 一文帶你掌握Golang Interface原理和使用技巧

    一文帶你掌握Golang Interface原理和使用技巧

    Golang 中的 interface 是一種非常重要的特性,可以讓我們寫出更加靈活的代碼。在本篇文章中,我們將深入探討 Golang 中interface 的原理和使用技巧,感興趣的可以了解一下
    2023-04-04
  • Go語言中的指針運(yùn)算實(shí)例分析

    Go語言中的指針運(yùn)算實(shí)例分析

    這篇文章主要介紹了Go語言中的指針運(yùn)算技巧,實(shí)例分析了Go語言指針運(yùn)算的實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Golang標(biāo)準(zhǔn)庫和外部庫的性能比較

    Golang標(biāo)準(zhǔn)庫和外部庫的性能比較

    這篇文章主要介紹Golang標(biāo)準(zhǔn)庫和外部庫的性能比較,下面文章講圍繞這兩點(diǎn)展開內(nèi)容,感興趣的小伙伴可以參考一下
    2021-10-10
  • Go語言指針使用分析與講解

    Go語言指針使用分析與講解

    這篇文章主要介紹了Go語言指針使用分析與講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • golang基礎(chǔ)之Gocurrency并發(fā)

    golang基礎(chǔ)之Gocurrency并發(fā)

    這篇文章主要介紹了golang基礎(chǔ)之Gocurrency并發(fā),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • Golang中的int類型和uint類型到底有多大?

    Golang中的int類型和uint類型到底有多大?

    int和uint類型在我們?nèi)粘i_發(fā)中經(jīng)常會用到,但有個(gè)疑問就是這兩個(gè)類型有多大,通過各種嘗試最終得到了答案,所以下面這篇文章主要給大家介紹了關(guān)于Golang中的int類型和uint類型到底有多大的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-12-12
  • Golang開發(fā)gRPC服務(wù)入門介紹

    Golang開發(fā)gRPC服務(wù)入門介紹

    這篇文章主要介紹了Golang開發(fā)gRPC服務(wù),Golang開發(fā)gRPC應(yīng)用程序的套路也已經(jīng)很清晰,這篇文章就來做一個(gè)簡單的介紹,算是入門,需要的朋友可以參考下
    2022-04-04
  • go程序執(zhí)行交叉編譯的流程步驟

    go程序執(zhí)行交叉編譯的流程步驟

    go程序可用通過交叉編譯的方式在一個(gè)平臺輸出多個(gè)平臺可運(yùn)行的二進(jìn)制包,本文給大家詳細(xì)介紹了go程序執(zhí)行交叉編譯的流程步驟,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下
    2024-07-07
  • 關(guān)于Go 是傳值還是傳引用?

    關(guān)于Go 是傳值還是傳引用?

    這篇文章主要討論Go語言 是傳值還是傳引用?文章先從Go 官方的定義展開,隨后是傳值和傳引用得介紹到map 和 slice得區(qū)別,需要的小伙伴可以參考一下文章得具體內(nèi)容
    2021-10-10

最新評論