You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何处理多配置?能否复用Configor读取代码及设计配置单例

复用配置加载代码与单例实现方案

当然可以复用LoadConfig函数来处理不同的配置结构体,同时也能实现单例模式确保配置只加载一次。下面结合Go的泛型(Go 1.18+支持)和sync包来给出具体方案:


一、通用配置加载函数(复用代码)

利用Go的泛型特性,我们可以编写一个通用的加载函数,支持任意配置结构体类型,无需为每个Config单独写重复的加载逻辑:

import "github.com/jinzhu/configor"

type Config1 struct {
    // 你的配置项示例
    AppName string `yaml:"app_name"`
    Port    int    `yaml:"port"`
}

type Config2 struct {
    // 另一个配置的项示例
    DBHost string `yaml:"db_host"`
    DBPort int    `yaml:"db_port"`
}

// LoadConfig 泛型函数,加载指定路径的配置到目标结构体
func LoadConfig[T any](configPath string) (T, error) {
    var configInstance T
    err := configor.Load(&configInstance, configPath)
    return configInstance, err
}

// 使用示例
func main() {
    // 加载Config1
    cfg1, err := LoadConfig[Config1]("configs/config1.yaml")
    if err != nil {
        panic("加载Config1失败: " + err.Error())
    }

    // 加载Config2
    cfg2, err := LoadConfig[Config2]("configs/config2.yaml")
    if err != nil {
        panic("加载Config2失败: " + err.Error())
    }
}

这个方案的优点是新增配置类型时无需修改加载函数,直接用LoadConfig[NewConfig](path)即可。


二、单例模式的配置实例

如果希望配置只加载一次(避免重复读取文件、保证全局配置一致性),可以结合泛型和缓存实现通用单例,或者为每个配置单独实现单例:

方案1:通用泛型单例(适合多配置类型场景)

通过一个线程安全的缓存存储已加载的配置,确保同一类型+路径的配置只加载一次:

import (
    "fmt"
    "sync"
    "github.com/jinzhu/configor"
)

var (
    configCache = make(map[string]any)
    cacheMutex  = sync.RWMutex{} // 读写锁保证并发安全
)

// SingletonLoadConfig 泛型单例加载函数,同一配置类型+路径仅加载一次
func SingletonLoadConfig[T any](configPath string) (T, error) {
    // 生成唯一缓存键:用类型+路径区分不同配置
    cacheKey := fmt.Sprintf("%T:%s", *new(T), configPath)

    // 先读锁检查缓存
    cacheMutex.RLock()
    if cachedCfg, exists := configCache[cacheKey]; exists {
        cacheMutex.RUnlock()
        return cachedCfg.(T), nil
    }
    cacheMutex.RUnlock()

    // 写锁加载并缓存
    cacheMutex.Lock()
    defer cacheMutex.Unlock()

    // 双重检查:防止并发场景下重复加载
    if cachedCfg, exists := configCache[cacheKey]; exists {
        return cachedCfg.(T), nil
    }

    // 加载配置
    cfg, err := LoadConfig[T](configPath)
    if err != nil {
        return *new(T), err
    }

    // 存入缓存
    configCache[cacheKey] = cfg
    return cfg, nil
}

// 使用示例
func main() {
    // 第一次调用:读取文件加载配置
    cfg1, err := SingletonLoadConfig[Config1]("configs/config1.yaml")
    if err != nil {
        panic(err)
    }

    // 第二次调用:直接返回缓存的实例,不会再读文件
    cfg1Cached, err := SingletonLoadConfig[Config1]("configs/config1.yaml")
}

方案2:单独配置的单例(适合配置类型较少的场景)

如果你的配置类型不多,也可以为每个配置单独实现单例,逻辑更直观,性能也略优(无需缓存map的开销):

import (
    "sync"
    "github.com/jinzhu/configor"
)

var (
    // Config1的单例实例和加载控制
    config1     Config1
    config1Once sync.Once
    config1Err  error

    // Config2的单例实例和加载控制
    config2     Config2
    config2Once sync.Once
    config2Err  error
)

// GetConfig1 获取Config1的单例实例
func GetConfig1() (Config1, error) {
    config1Once.Do(func() {
        config1Err = configor.Load(&config1, "configs/config1.yaml")
    })
    return config1, config1Err
}

// GetConfig2 获取Config2的单例实例
func GetConfig2() (Config2, error) {
    config2Once.Do(func() {
        config2Err = configor.Load(&config2, "configs/config2.yaml")
    })
    return config2, config2Err
}

// 使用示例
func main() {
    // 第一次调用加载配置,后续调用直接返回实例
    cfg1, err := GetConfig1()
}

注意事项

  1. 错误处理:上述示例中加入了错误返回,实际项目中不要忽略configor.Load的错误(比如文件不存在、格式错误等)。
  2. 泛型版本要求:泛型方案需要Go 1.18及以上版本,如果你的项目使用更低版本的Go,可能需要用反射来实现通用加载,但反射的可读性和性能不如泛型。
  3. 配置变更:如果需要支持配置热重载,上述单例方案需要额外处理(比如监听文件变更,更新缓存或实例)。

内容的提问来源于stack exchange,提问作者Relax ZeroC

火山引擎 最新活动