go 初始化组件最佳实践
Go 语言初始化最佳实践
在 Go 语言中, 有一个 init()
函数可以对程序进行包级别的初始化, 但 init()
函数有诸多不便, 例如: 无法返回错误, 进行耗时初始化时, 会增加程序启动时间。因此 init()
函数并不适用于所有初始化。
1.初始化方式
在程序进行初始化时,我们应该分析初始化的对象是为: 急切初始化
(如: 日志组件, 配置文件读取) 还是 延迟初始化
(如: 数据库连接, 消息队列连接) 。急切初始化大多情况不需要对外部进行连接,且被其他组件所依赖,这时使用 init()
函数进行初始化。延迟初始化对象为需要程序对外部进行连接,且耗时较长, 这时调用 sync.once
进行初始化保证并发安全。
特性 | 延迟初始化 | 使用 init 函数 进行 急切初始化 |
---|---|---|
执行时机 | 首次调用时执行 (懒加载) | 包被导入时自动执行,在 main 函数之前 (急加载) |
并发安全 | 调用 ,专为并发环境设计 | 包初始化由运行时控制,但后续对全局变量的并发访问需额外同步 |
错误处理 | 可通过封装返回 error (需自行实现) | 困难,通常只能 log.Fatal 或 panic |
资源消耗 | 按需分配,避免不必要的启动开销 | 程序启动即分配,可能增加启动时间和内存占用 |
典型应用场景 | 数据库连接池 、外部服务客户端、重型单例对象 | 配置预加载(轻量)、驱动注册(如数据库驱动)、日志系统初始化 |
可控性 | 高,可灵活控制初始化时机和条件 | 低,由 Go 运行时控制执行顺序和时机 |
2.单例模式
单例模式是一种创建型设计模式,它的核心目标是确保一个结构体只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问或确保全局唯一性的场景中非常有用。
单例模式的关键在于三点:
- 唯一实例:单例结构体必须保证只创建一个对象实例。
- 自我创建:单例结构体需要自行创建这个实例。
- 全局访问:单例结构体必须提供一个允许全局访问该实例的方法。
实现上,通常不允许外部修改结构体,然后提供一个公共方法(如 getInstance()
)来返回该类的唯一实例。
实现方式 | 描述 | 优点 | 缺点 | 并发安全 |
---|---|---|---|---|
饿汉式 | 类加载时就初始化实例。 | 实现简单,线程安全 | 未使用实例时也会创建,可能浪费内存 | 是 |
懒汉式 | 第一次调用时才创建实例。 | 延迟加载,节省资源 | 需加锁保证保证安全,性能有开销 | 是 |
init()
就是饿汉式, 懒汉式 使用 sync.once
, 且 sync.once
内部的优化保证了性能和并发安全。
3.代码示例
饿汉式示例:
package confimport ("fmt""github.com/joho/godotenv""github.com/spf13/viper""log""time"
)type Config struct {App AppConfig
}var _conf = &Config{}func init() {var v = viper.New()v.SetConfigName("StarMall")v.SetConfigType("toml")v.AddConfigPath("E:/starmall/")v.AutomaticEnv()// 读取配置文件err := v.ReadInConfig()if err != nil {fmt.Println(err)log.Printf("config load Error: %v \n", err)} else {log.Println("configuration file was read successfully")}// 将 viper 读到的数据序列化写入 configif err := v.Unmarshal(&_conf); err != nil {now := time.Now()log.Printf("%v: viper Unmarshal err:%s \n", now.Format("2006-01-02 15:04:05"), err)}
}func GetConfig() *Config {return _conf
}
懒汉式 示例:
package databaseimport ("fmt"_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx""github.com/star-find-cloud/star-mall/conf"log "github.com/star-find-cloud/star-mall/pkg/logger""sync"
)type MySQL struct {Conn *sqlx.DB
}var (_mysql = &MySQL{}once sync.Once
)func initMysql() (*sqlx.DB, error) {var (_db *sqlx.DBerr error)once.Do(func() {c := conf.GetConfig()user := c.Database.MySQL.Userpasswd := c.Database.MySQL.PasswordHost := c.Database.MySQL.MasterHostPort := c.Database.MySQL.MasterPorttimeout := c.Database.MySQL.TimeoutDSN := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&parseTime=True&timeout=%ss", user, passwd, Host, Port, timeout)_db, err = sqlx.Connect("mysql", DSN)if err != nil {fmt.Println("Database error, please check the logs.")//fmt.Println(c.Database.MySQL)log.MySQLLogger.Errorf("MySQL master connect faild: %s \n", err)} else {log.MySQLLogger.Infof("MySQL master connection successful: %s\n", Host)}_db.SetMaxOpenConns(c.Database.MySQL.MaxOpenConns)_db.SetMaxIdleConns(c.Database.MySQL.MaxIdleConns)})return _db, err
}func NewMySQL() (*MySQL, error) {db, err := initMysql()_mysql.Conn = dbreturn _mysql, err
}