如何给Gone框架编写Goner组件(上)——编写一个Goner对接Apollo配置中心

  • 如何给Gone框架编写Goner组件[上]------编写一个Goner对接Apollo配置中心
    • 引言
    • Gone框架与Goner组件简介
    • Apollo配置中心简介
    • [编写Apollo Goner组件的核心思路](#编写Apollo Goner组件的核心思路 "#%E7%BC%96%E5%86%99apollo-goner%E7%BB%84%E4%BB%B6%E7%9A%84%E6%A0%B8%E5%BF%83%E6%80%9D%E8%B7%AF")
    • 核心代码实现与讲解
      • [1. Apollo客户端组件实现](#1. Apollo客户端组件实现 "#1-apollo%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BB%84%E4%BB%B6%E5%AE%9E%E7%8E%B0")
      • [2. 初始化Apollo客户端](#2. 初始化Apollo客户端 "#2-%E5%88%9D%E5%A7%8B%E5%8C%96apollo%E5%AE%A2%E6%88%B7%E7%AB%AF")
      • [3. 配置获取实现](#3. 配置获取实现 "#3-%E9%85%8D%E7%BD%AE%E8%8E%B7%E5%8F%96%E5%AE%9E%E7%8E%B0")
      • [4. 配置值设置工具函数](#4. 配置值设置工具函数 "#4-%E9%85%8D%E7%BD%AE%E5%80%BC%E8%AE%BE%E7%BD%AE%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0")
      • [5. 配置监听和自动更新依赖注入的值](#5. 配置监听和自动更新依赖注入的值 "#5-%E9%85%8D%E7%BD%AE%E7%9B%91%E5%90%AC%E5%92%8C%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5%E7%9A%84%E5%80%BC")
      • 提供gone.LoadFunc函数,方便使用
    • [使用Apollo Goner组件的示例](#使用Apollo Goner组件的示例 "#%E4%BD%BF%E7%94%A8apollo-goner%E7%BB%84%E4%BB%B6%E7%9A%84%E7%A4%BA%E4%BE%8B")
      • [1. 编写本地配置文件,支持多种配置格式:JSON、YAML、TOML、Properties 等](#1. 编写本地配置文件,支持多种配置格式:JSON、YAML、TOML、Properties 等 "#1-%E7%BC%96%E5%86%99%E6%9C%AC%E5%9C%B0%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%94%AF%E6%8C%81%E5%A4%9A%E7%A7%8D%E9%85%8D%E7%BD%AE%E6%A0%BC%E5%BC%8Fjsonyamltomlproperties-%E7%AD%89")
      • [2. 在服务中使用Apollo配置](#2. 在服务中使用Apollo配置 "#2-%E5%9C%A8%E6%9C%8D%E5%8A%A1%E4%B8%AD%E4%BD%BF%E7%94%A8apollo%E9%85%8D%E7%BD%AE")
      • [3. 引入Apollo组件](#3. 引入Apollo组件 "#3-%E5%BC%95%E5%85%A5apollo%E7%BB%84%E4%BB%B6")
    • 高级用法
      • [1. 监听配置变更](#1. 监听配置变更 "#1-%E7%9B%91%E5%90%AC%E9%85%8D%E7%BD%AE%E5%8F%98%E6%9B%B4")
      • [2. 支持多命名空间](#2. 支持多命名空间 "#2-%E6%94%AF%E6%8C%81%E5%A4%9A%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4")
    • 最佳实践
    • 结论
    • 参考资源

引言

在微服务架构中,配置中心是一个非常重要的基础设施,它能够集中管理各个服务的配置信息,实现配置的动态更新。Apollo是携程开源的一款优秀的分布式配置中心,本文将详细讲解如何基于Gone框架编写一个Goner组件对接Apollo配置中心,实现配置的统一管理。

Gone框架与Goner组件简介

Gone是一个基于Go语言的依赖注入框架,而Goner则是基于Gone框架开发的可复用组件。通过编写Goner组件,我们可以将特定功能模块化,便于在不同项目中复用。

Apollo配置中心简介

Apollo配置中心主要由以下部分组成:

  • 配置管理界面(Portal):供用户管理配置
  • 配置服务(ConfigService):提供配置获取接口
  • 客户端SDK:与服务端交互,获取/监听配置变化

编写Apollo Goner组件的核心思路

  1. 先通过goner viper获取本地关于Apollo连接的配置信息
  2. 封装Apollo客户端,提供配置获取监听能力
  3. 实现gone.Configure接口,将Apollo配置中心的值直接注入到需要的组件中
  4. 实现配置自动更新机制,监控配置变更,并更新对应的变量值
  5. 支持不同类型配置的解析和转换

核心代码实现与讲解

1. Apollo客户端组件实现

源代码:apollo/client.go

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
type apolloClient struct {
    gone.Flag
    localConfigure gone.Configure
    apolloClient   agollo.Client

    changeListener *changeListener `gone:"*"`
    testFlag       gone.TestFlag   `gone:"*" option:"allowNil"`
    logger         gone.Logger     `gone:"*" option:"lazy"`

    appId                     string //`gone:"config,apollo.appId"`
    cluster                   string //`gone:"config,apollo.cluster"`
    ip                        string //`gone:"config,apollo.ip"`
    namespace                 string //`gone:"config,apollo.namespace"`
    secret                    string //`gone:"config,apollo.secret"`
    isBackupConfig            bool   //`gone:"config,apollo.isBackupConfig"`
    watch                     bool   //`gone:"config,apollo.watch"`
    useLocalConfIfKeyNotExist bool   //`gone:"config,apollo.useLocalConfIfKeyNotExist"`
}

apolloClient结构体定义了Apollo客户端组件的各个字段:

  • 依赖注入的组件:changeListenertestFlaglogger
  • Apollo配置项:appIdclusterip
  • 控制选项:watch(是否监听配置变更)、useLocalConfIfKeyNotExist(当配置不存在时是否使用本地配置)

2. 初始化Apollo客户端

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
func (s *apolloClient) Init() {
    s.localConfigure = viper.New(s.testFlag)

    m := map[string]*tuple{
       "apollo.appId":                     {v: &s.appId, defaultVal: ""},
       "apollo.cluster":                   {v: &s.cluster, defaultVal: "default"},
       "apollo.ip":                        {v: &s.ip, defaultVal: ""},
       "apollo.namespace":                 {v: &s.namespace, defaultVal: "application"},
       "apollo.secret":                    {v: &s.secret, defaultVal: ""},
       "apollo.isBackupConfig":            {v: &s.isBackupConfig, defaultVal: "true"},
       "apollo.watch":                     {v: &s.watch, defaultVal: "false"},
       "apollo.useLocalConfIfKeyNotExist": {v: &s.useLocalConfIfKeyNotExist, defaultVal: "true"},
    }
    for k, t := range m {
       err := s.localConfigure.Get(k, t.v, t.defaultVal)
       if err != nil {
          panic(err)
       }
    }

    c := &config.AppConfig{
       AppID:          s.appId,
       Cluster:        s.cluster,
       IP:             s.ip,
       NamespaceName:  s.namespace,
       IsBackupConfig: s.isBackupConfig,
       Secret:         s.secret,
    }
    client, err := agollo.StartWithConfig(func() (*config.AppConfig, error) {
       return c, nil
    })
    if err != nil {
       panic(err)
    }
    s.apolloClient = client
    if s.watch {
       client.AddChangeListener(s.changeListener)
    }
}

Init方法完成了Apollo客户端的初始化工作:

  1. 创建本地配置源(使用viper组件)
  2. 从本地配置中读取Apollo相关配置项
  3. 创建Apollo客户端配置
  4. 启动Apollo客户端
  5. 如果启用了配置监听,则添加变更监听器

3. 配置获取实现

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
func (s *apolloClient) Get(key string, v any, defaultVal string) error {
    if s.watch {
       s.changeListener.Put(key, v)
    }

    if s.apolloClient == nil {
       return s.localConfigure.Get(key, v, defaultVal)
    }

    namespaces := strings.Split(s.namespace, ",")
    for _, ns := range namespaces {
       cache := s.apolloClient.GetConfigCache(ns)
       if cache != nil {
          if value, err := cache.Get(key); err == nil {
             err = setValue(v, value)
             if err != nil {
                s.warnf("try to set `%s` value err:%v\n", key, err)
             } else {
                return nil
             }
          } else {
             s.warnf("get `%s` value from apollo ns(%s) err:%v\n", key, ns, err)
          }
       }
    }
    if s.useLocalConfIfKeyNotExist {
       return s.localConfigure.Get(key, v, defaultVal)
    }
    return nil
}

Get方法是获取配置的核心实现:

  1. 如果启用了配置监听,则将配置键与变量引用关联起来
  2. 如果Apollo客户端未初始化,则从本地配置获取
  3. 遍历所有命名空间,尝试从Apollo获取配置
  4. 如果获取成功,则将配置值设置到变量中
  5. 如果从Apollo获取失败且允许使用本地配置,则从本地配置获取

4. 配置值设置工具函数

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
func setValue(v any, value any) error {
    if str, ok := value.(string); ok {
       return gone.ToError(gone.SetValue(reflect.ValueOf(v), v, str))
    } else {
       marshal, err := json.Marshal(value)
       if err != nil {
          return gone.ToError(err)
       }
       return gone.ToError(gone.SetValue(reflect.ValueOf(v), v, string(marshal)))
    }
}

setValue函数用于将配置值设置到变量中:

  1. 如果配置值是字符串,则直接设置
  2. 如果配置值是其他类型,则先转换为JSON字符串,再设置

5. 配置监听和自动更新依赖注入的值

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
type changeListener struct {
    gone.Flag
    keyMap map[string]any
    logger gone.Logger `gone:"*" option:"lazy"`
}

func (c *changeListener) Init() {
    c.keyMap = make(map[string]any)
}

func (c *changeListener) Put(key string, v any) {
    c.keyMap[key] = v
}

func (c *changeListener) OnChange(event *storage.ChangeEvent) {
    for k, change := range event.Changes {
        if v, ok := c.keyMap[k]; ok && change.ChangeType == storage.MODIFIED {
            err := setValue(v, change.NewValue)
            if err != nil && c.logger != nil {
                c.logger.Warnf("try to change `%s` value  err: %v\n", k, err)
            }
        }
    }
}

func (c *changeListener) OnNewestChange(*storage.FullChangeEvent) {}

changeListener实现了Apollo客户端的配置变更监听接口:

  • Init方法初始化一个map用于存储配置键与对应的变量引用
  • Put方法将配置键与变量引用关联起来
  • OnChange方法在配置变更时被调用,它会遍历所有变更的配置,找到对应的变量引用,并更新其值
  • OnNewestChange方法是接口要求实现的,但在本例中没有具体逻辑

提供gone.LoadFunc函数,方便使用

go:https://github.com/gone-io/goner/blob/goner-example/apollo/client.go 复制代码
var load = gone.OnceLoad(func(loader gone.Loader) error {
    err := loader.
        Load(
            &apolloClient{},
            gone.Name(gone.ConfigureName),
            gone.IsDefault(new(gone.Configure)),
            gone.ForceReplace(),
        )
    if err != nil {
        return err
    }
    return loader.Load(&changeListener{})
})

func Load(loader gone.Loader) error {
    return load(loader)
}

这段代码使用gone.OnceLoad确保组件只被加载一次,并通过loader.Load方法注册了两个组件:

  • apolloClient:实现了gone.Configure接口,用于获取配置
  • changeListener:用于监听配置变更

使用Apollo Goner组件的示例

1. 编写本地配置文件,支持多种配置格式:JSON、YAML、TOML、Properties 等

yml 复制代码
# config/default.yml
apollo:
  appId: SampleApp
  cluster: default
  ip: http://127.0.0.1:8080
  namespace: application,test.yml
  secret: your-secret
  isBackupConfig: false
  watch: false
  useLocalConfIfKeyNotExist: true

2. 在服务中使用Apollo配置

go 复制代码
type MyService struct {
    gone.Flag
    
    // 服务配置
    serverPort int `gone:"config,server.port"`
    timeout    int `gone:"config,service.timeout"`
}

func (s *MyService) Init() {
    // 使用配置
    fmt.Printf("服务启动,端口:%d,超时:%d毫秒\n", s.serverPort, s.timeout)
}

3. 引入Apollo组件

go 复制代码
import (
    "github.com/gone-io/gone/v2"
    "github.com/gone-io/goner/apollo"
)

func main() {
    gone.
       Loads(
          apollo.Load,
          // 其他组件...
       ).
       Load(&MyService{}).
       Run(func() {
          // ...
        })
}

高级用法

1. 监听配置变更

通过启用apollo.watch配置,可以实现配置的自动更新:

注意 : 需要动态更新的字段,必须使用指针类型才有效。

go 复制代码
type MyService struct {
    gone.Flag
    
    // 服务配置
    serverPort *int `gone:"config,server.port"`
    timeout    *int `gone:"config,service.timeout"`
}

func (s *MyService) Init() {
    
    go func() {
       for {
            fmt.Printf("服务启动,端口:%d,超时:%d毫秒\n", *s.serverPort, *s.timeout)
          time.Sleep(2 * time.Second)
        }
    }
}

2. 支持多命名空间

Apollo支持多个命名空间,可以通过逗号分隔的方式在apollo.namespace中指定:

yml 复制代码
# config/default.yml

# 在配置文件中设置
apollo.namespace: application,test.yml,database

注意:不是properties类型的namespace需要带后缀名才能正常的从Apollo上获取到值。

这样,在获取配置时会依次从这些命名空间中查找。

最佳实践

  1. 配置分层管理:将配置按照应用、环境、集群等维度进行分层管理

  2. 默认值处理:获取配置时始终提供合理的默认值,避免因配置缺失导致应用崩溃

  3. 优雅降级:当Apollo服务不可用时,应用应能够使用本地缓存的配置继续运行

  4. 配置变更验证:对于关键配置的变更,应进行合理性验证,避免错误配置导致系统问题

  5. 监控与告警:对配置获取失败、配置变更等关键事件进行监控和告警

结论

通过本文的讲解,我们了解了如何基于Gone框架编写一个Goner组件对接Apollo配置中心,实现配置的统一管理。这种方式不仅简化了配置管理的复杂度,还提供了配置动态更新的能力,使应用更加灵活和可维护。

在实际项目中,我们可以根据需求对Apollo Goner组件进行扩展,例如添加更多的配置类型支持、增强配置变更的处理逻辑等,以满足不同场景的需求。

参考资源

  1. Apollo官方文档:www.apolloconfig.com/
  2. Gone框架文档:github.com/gone-io/gon...
  3. Apollo Go客户端:github.com/apolloconfi...
相关推荐
hikktn几秒前
【开源宝藏】30天学会CSS - DAY9 第九课 牛顿摆动量守恒动画
前端·css·开源
字节跳动开源1 分钟前
MySQL遇到AI:字节跳动开源 MySQL 虚拟索引 VIDEX
人工智能·mysql·开源·虚拟索引技术·解耦架构
钝挫力PROGRAMER1 小时前
架构级代码复用实战:从继承泛型到函数式接口的深度重构
重构·架构
Q186000000001 小时前
springboot 四层架构之间的关系整理笔记一
spring boot·后端·架构
亿坊电商2 小时前
开源的CMS建站系统可以随便用吗?有什么需要注意的?
开源·开源cms系统
weiran19992 小时前
手把手的建站思路和dev-ops方案
前端·后端·架构
_xaboy3 小时前
基于Vue的低代码可视化表单设计器 FcDesigner 3.2.11更新说明
前端·vue.js·低代码·开源·表单设计器
刀法如飞4 小时前
探索MVC、MVP、MVVM和DDD架构在不同编程语言中的实现差异
架构·mvc·软件构建
Wnq100725 小时前
智慧城市智慧调度系统的架构与关键技术研究
人工智能·架构·智慧城市·big data
星辰大海的精灵5 小时前
SpringAI轻松构建MCP Client-Server架构
人工智能·后端·架构