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
}
相关推荐
用户93761147581616 分钟前
并发编程三大特性
java·后端
阿在在8 分钟前
Spring 系列(二):加载 BeanDefinition 的几种方式
java·后端·spring
颜酱10 分钟前
前端算法必备:双指针从入门到很熟练(快慢指针+相向指针+滑动窗口)
前端·后端·算法
p***s9119 分钟前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
AI架构师之家21 分钟前
一文分清机器学习、深度学习和各类 AI 工具的关系:心法与招式的区别
后端·ai编程
neoooo23 分钟前
🍃Spring Boot 多模块项目中 Parent / BOM / Starter 的正确分工
java·后端·架构
菜鸟的迷茫24 分钟前
为了防雪崩加了限流,结果入口先挂了
java·后端·架构
悟空码字29 分钟前
SpringBoot整合MongoDB,性能提升,优化实践
java·spring boot·后端
No芒柠Exception29 分钟前
从开发到上线的CI/CD 完整流程
后端·面试·架构
天若有情6731 小时前
Spring Boot 前后端联调3大经典案例:从入门到实战(通俗易懂版)
spring boot·后端·状态模式