go 初始化组件最佳实践

Go 语言初始化最佳实践

在 Go 语言中, 有一个 init() 函数可以对程序进行包级别的初始化, 但 init() 函数有诸多不便, 例如: 无法返回错误, 进行耗时初始化时, 会增加程序启动时间。因此 init() 函数并不适用于所有初始化。

1.初始化方式

在程序进行初始化时,我们应该分析初始化的对象是为: 急切初始化(如: 日志组件, 配置文件读取) 还是 延迟初始化 (如: 数据库连接, 消息队列连接) 。急切初始化大多情况不需要对外部进行连接,且被其他组件所依赖,这时使用 init() 函数进行初始化。延迟初始化对象为需要程序对外部进行连接,且耗时较长, 这时调用 sync.once 进行初始化保证并发安全。

特性 延迟初始化 使用 init 函数 进行 急切初始化
执行时机 首次调用时执行 (懒加载) 包被导入时自动执行,在 main 函数之前 (急加载)
并发安全 调用 ,专为并发环境设计 包初始化由运行时控制,但后续对全局变量的并发访问需额外同步
错误处理 可通过封装返回 error (需自行实现) 困难,通常只能 log.Fatalpanic
资源消耗 按需分配,避免不必要的启动开销 程序启动即分配,可能增加启动时间和内存占用
典型应用场景 数据库连接池外部服务客户端重型单例对象 配置预加载 (轻量)、驱动注册 (如数据库驱动)、日志系统初始化
可控性 高,可灵活控制初始化时机和条件 低,由 Go 运行时控制执行顺序和时机

2.单例模式

单例模式是一种创建型设计模式,它的核心目标是确保一个结构体只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问或确保全局唯一性的场景中非常有用。

单例模式的关键在于三点:

  1. 唯一实例:单例结构体必须保证只创建一个对象实例。
  2. 自我创建:单例结构体需要自行创建这个实例。
  3. 全局访问:单例结构体必须提供一个允许全局访问该实例的方法。

实现上,通常不允许外部修改结构体,然后提供一个公共方法 (如 getInstance())来返回该类的唯一实例。

实现方式 描述 优点 缺点 并发安全
饿汉式 类加载时就初始化实例。 实现简单,线程安全 未使用实例时也会创建,可能浪费内存
懒汉式 第一次调用时才创建实例。 延迟加载,节省资源 需加锁保证保证安全,性能有开销

init() 就是饿汉式 , 懒汉式 使用 sync.once , 且 sync.once 内部的优化保证了性能和并发安全。

3.代码示例

饿汉式示例:

go 复制代码
package conf

import (
	"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 读到的数据序列化写入 config
	if 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
}

懒汉式 示例:

go 复制代码
package database

import (
	"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.DB
		err error
	)
	once.Do(func() {
		c := conf.GetConfig()
		user := c.Database.MySQL.User
		passwd := c.Database.MySQL.Password
		Host := c.Database.MySQL.MasterHost
		Port := c.Database.MySQL.MasterPort
		timeout := c.Database.MySQL.Timeout
		DSN := 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 = db
	return _mysql, err
}
相关推荐
老夫的码又出BUG了1 天前
分布式Web应用场景下存在的Session问题
前端·分布式·后端
研究司马懿1 天前
【ETCD】ETCD——confd配置管理
数据库·golang·自动化·运维开发·etcd·argocd·gitops
L.EscaRC1 天前
Spring Boot 自定义组件深度解析
java·spring boot·后端
金銀銅鐵1 天前
[Java] JDK 9 新变化之 Convenience Factory Methods for Collections
java·后端
微小冷1 天前
Rust图形界面教程:egui基础组件的使用
后端·rust·gui·egui·button·panel·用户图形界面
javadaydayup1 天前
同样是简化代码,Lambda 和匿名内部类的核心原理是什么?
后端
Yeats_Liao1 天前
时序数据库系列(六):物联网监控系统实战
数据库·后端·物联网·时序数据库
金銀銅鐵1 天前
[Java] 用 Swing 生成一个最大公约数计算器
java·后端
brzhang1 天前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
苏三的开发日记1 天前
库存预扣减之后,用户订单超时之后补偿库存的方案
后端