我相信大多数开发者在编写应用程序时,都会面临一个基本问题:如何在应用启动时进行配置。我通常使用环境变量(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 来加载配置。这将帮助我们始终保持配置的最新状态。
结论
现在,你可以使用这个配置管理器来改进你的系统,实现动态配置,从而简化开发工作。