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
}
相关推荐
callJJ1 分钟前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(2)
java·开发语言·后端·spring·ioc·di
你的人类朋友2 小时前
JWT的组成
后端
phdsky2 小时前
【设计模式】策略模式
设计模式·策略模式
北风朝向3 小时前
Spring Boot参数校验8大坑与生产级避坑指南
java·spring boot·后端·spring
烛阴3 小时前
【TS 设计模式完全指南】构建你的专属“通知中心”:深入观察者模式
javascript·设计模式·typescript
canonical_entropy3 小时前
一份关于“可逆计算”的认知解码:从技术细节到哲学思辨的完整指南
后端·低代码·deepseek
趙卋傑4 小时前
项目发布部署
linux·服务器·后端·web
Mr_WangAndy4 小时前
C++设计模式_创建型模式_原型模式Prototype
c++·设计模式·原型模式
数据知道5 小时前
Go基础:Go语言能用到的常用时间处理
开发语言·后端·golang·go语言
不爱编程的小九九5 小时前
小九源码-springboot048-基于spring boot心理健康服务系统
java·spring boot·后端