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.代码示例

饿汉式示例:

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
}

懒汉式 示例:

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
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值