DConf —— 应用的动态配置

我相信大多数开发者在编写应用程序时,都会面临一个基本问题:如何在应用启动时进行配置。我通常使用环境变量(ENVs)来设置一些可以根据运行环境或时间改变的值。然而,这些配置仅在应用启动时生效。如果我们需要更改配置,该怎么办呢?显然,我们需要用新的配置值重启应用。在某些情况下,这样做是合理的,但有些配置值可以是动态的,对吧?

让我们来看一个虚构的应用,它用于我们的金融项目。假设我们有一个应用程序,它处理用户的交易,并需要计算一些费用和控制用户的限制(即用户在其订阅计划期间允许执行的交易次数)。有一些管理员可以手动管理系统设置。例如,今天是一个假日,我们希望给用户更多的机会:增加限制并降低交易费用。由于我们使用的是传统的配置方式,我们需要更改配置并重启所有应用实例。但如果我们有一些动态配置,我们就可以在不重启的情况下实现这一目标!

因此,让我们创建我们的 DConf——动态配置。

项目初始化

在这个例子中,我们将使用 Golang。首先,创建项目目录并初始化一些命令:

# 创建项目目录,假设我们在 ~/go/src/github.com/myaccount
mkdir dconf
# 初始化应用
go mod init

应用架构设计

我们需要一个管理服务来管理配置的所有操作,并使用数据库(DB)。因此,创建一个管理包:

mkdir manager

接下来,我们需要为将要使用的数据库存储库定义一个接口。这个存储库应该能够从数据库中提供配置。此外,我们可以添加一个方法来更新配置,以便我们希望从应用中更新它或进行一些测试。

创建文件并定义存储库接口:

touch manager/interface.go

package manager

import (
    "context"
)

type Repository interface {
    UpdateConfig(ctx context.Context, value any) error
    GetConfig(ctx context.Context, obj, defaultValue any) error
}

管理器实现

定义管理器结构:

touch manager/manager.go

type ConfigManager[T any] struct {
    repo         Repository
    scanInterval time.Duration
    mx           sync.Mutex
    dynCfg       T
}

func New[T any](repo Repository, initCfg T, scanInterval time.Duration) *ConfigManager[T] {
    return &ConfigManager[T]{
        dynCfg:       initCfg,
        repo:         repo,
        scanInterval: scanInterval,
    }
}

repo 是我们的存储库实例,scanInterval 是调用存储库的 GetConfig 方法的时间间隔,mx 是用于防止配置使用时竞争的互斥锁,dynCfg 是当前实际的动态应用配置。

由于我们的配置结构可以是任何类型,因此我们使用泛型声明配置管理器。

加载配置方法

我们需要一个方法从存储库加载配置:

func (m *ConfigManager[T]) LoadConfig(ctx context.Context) error {
    var cfg T
    if err := m.repo.GetConfig(ctx, &cfg, m.dynCfg); err != nil {
        return err
    }
    m.mx.Lock()
    defer m.mx.Unlock()
    m.dynCfg = cfg
    return nil
}

获取配置方法

获取当前配置的方法:

func (m *ConfigManager[T]) GetConfig() T {
    m.mx.Lock()
    defer m.mx.Unlock()
    return m.dynCfg
}

定时更新配置

为了始终保持配置的最新状态,我们需要添加一个后台执行任务,每隔 N 秒从存储库中获取配置:

func (m *ConfigManager[T]) Run(ctx context.Context, wg *sync.WaitGroup) error {
    if err := m.LoadConfig(ctx); err != nil {
        return err
    }
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case <-ctx.Done():
                return
            case <-time.After(m.scanInterval):
                if err := m.LoadConfig(ctx); err != nil {
                    log.Printf("error while loading the config: %v", err)
                }
            }
        }
    }()
    return nil
}

Run 方法中,我们进行:

  • 初始配置加载:应用启动时,我们希望从分布式存储中获取实际配置,而不是使用默认值。
  • 每隔 N 秒运行一个 goroutine 来加载配置。这将帮助我们始终保持配置的最新状态。

结论

现在,你可以使用这个配置管理器来改进你的系统,实现动态配置,从而简化开发工作

posted @ 2024-12-01 13:14  技术颜良  阅读(10)  评论(0编辑  收藏  举报