GO语言使用gorm的dbresolver插件实现数据库读写分离

GO语言使用gorm的dbresolver插件实现数据库读写分离

settings.yaml文件中读库跟写库的配置信息:db:读库,db1:写库

yaml 复制代码
db:
  user: root
  password: 123456
  host: 127.0.0.1
  port: 3306
  db: blogx
  debug: false
  source: mysql
db1:
  user: root
  password: 123456
  host: 127.0.0.1
  port: 3306
  db: test
  debug: false
  source: mysql

conf/enter.go

go 复制代码
type Config struct {
	System System `yaml:"system"`
	Log    Log    `yaml:"log"`
	DB     DB     `yaml:"db"`  //读库
	DB1    DB     `yaml:"db1"` //写库
}

conf/conf_db.go

go 复制代码
type DB struct {
	User     string `yaml:"user"`
	Password string `yaml:"password"`
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	DB       string `yaml:"db"`
	Debug    bool   `yaml:"debug"`  //打印全部日志
	Source   string `yaml:"source"` //数据库的源 MySQL pgsql
}

func (d DB) DSN() string {
	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
		d.User, d.Password, d.Host, d.Port, d.DB)
}
func (d DB) Empty() bool {
	return d.User == "" && d.Password == "" && d.Host == "" && d.Port == 0
}

core/init_db.go: 利用 GORM 的 dbresolver 插件,把两份配置注册成 "Sources / Replicas"

go 复制代码
import (
	"blogx_server/global"
	"github.com/sirupsen/logrus"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/plugin/dbresolver"
	"time"
)

func InitDB() *gorm.DB {
	dc := global.Config.DB   //读库
	dc1 := global.Config.DB1 //写库
	db, err := gorm.Open(mysql.Open(dc.DSN()), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true, // 表迁移时不自动创建外键约束
	})
	if err != nil {
		panic(err)
	}
	sqlDB, err := db.DB()
	sqlDB.SetMaxIdleConns(10)           // 空闲连接池大小
	sqlDB.SetMaxOpenConns(100)          // 最大打开连接数
	sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间
	logrus.Info("数据库连接成功")
	if !dc1.Empty() {
		//读写库不为空,就注册读写分离的配置
		err = db.Use(dbresolver.Register(dbresolver.Config{
			Sources:  []gorm.Dialector{mysql.Open(dc1.DSN())}, //写
			Replicas: []gorm.Dialector{mysql.Open(dc.DSN())},  //读
			Policy: dbresolver.RandomPolicy{},
		}))
		if err != nil {
			logrus.Fatalf("读写配置错误:%s", err)
		}

	}

	return db
}

项目的"数据库读写分离"功能依赖 3 处代码:

  1. 配置层
    settings.yaml 里的 db:(读库)和 db1:(写库)
    conf/conf_db.go 里的 DB 结构体 + Empty() 方法
  2. 启动层
    core/init_db.go 中利用 GORM 的 dbresolver 插件,把两份配置注册成 "Sources / Replicas"
  3. 业务层
    • 业务代码照常用 db.Create / db.Find...,GORM 自动把写请求导向写库,把读请求导向读库。

下面循序渐进地把每一步拆开讲。

──────────────────────────────────────

一、YAML + 结构体:怎样描述两套数据库

  1. settings.yaml(缩写示例)

    db: # 读库
    user: root
    password: 123456
    host: 127.0.0.1
    port: 3306
    db: blogx
    debug: false
    source: mysql

    db1: # 写库
    user: root
    password: 123456
    host: 192.168.1.10
    port: 3306
    db: blogx
    debug: false
    source: mysql

  2. conf/enter.go

go 复制代码
type Config struct {
    System System `yaml:"system"`
    Log    Log    `yaml:"log"`
    DB     DB     `yaml:"db"`   // 读
    DB1    DB     `yaml:"db1"`  // 写
}
  1. conf/conf_db.go
    DB.DSN() 把账号/主机/库名拼成 MySQL 连接串;
    DB.Empty() 判断该配置是否完全为空(四项都没填);后面用于"若用户没配写库就别启动读写分离"。

──────────────────────────────────────

二、InitDB:用 dbresolver 把"读 / 写"连起来

核心流程位于 core/init_db.go

go 复制代码
dc  := global.Config.DB   // 读库
dc1 := global.Config.DB1  // 写库
db, _ := gorm.Open(mysql.Open(dc.DSN()), &gorm.Config{...}) // 先连读库
...
if !dc1.Empty() {         // 写库不为空才开启读写分离
    err = db.Use(dbresolver.Register(dbresolver.Config{
        Sources:  []gorm.Dialector{mysql.Open(dc1.DSN())}, // 写
        Replicas: []gorm.Dialector{mysql.Open(dc.DSN())},  // 读
        Policy:   dbresolver.RandomPolicy{},               // 读负载均衡策略
    }))
    ...
}

细节说明:

  1. 先用读库 DSN 建立基础连接------这样即便只配一套库,也能正常跑起来。

  2. 判断写库是否留空 ------dc1.Empty() 会返回 true/false:

    • 如果用户没有在 YAML 里填 db1:,代码就不会注册 dbresolver,整个项目退化成"单库"模式。

    • 如果配了,才执行 db.Use(...),将两套连接池交给插件管理。

  3. Sources / Replicas 的含义

    Sources:主库(写库)。GORM 会把所有会写入数据的操作(Create、Update、Delete、Exec、事务)发送到这里。

    Replicas:从库(读库)。所有纯查询(First、Find、Scan、Raw SQL 查询)会走这里。

  4. Policy :当有多台从库时负责负载均衡。此处用 RandomPolicy,即随机挑一台;GORM 还内置 RoundRobinPolicy 等策略。

  5. 错误处理 :如果 db.Use 返回 err,立即 logrus.Fatalf(...) 终止启动,避免带错配置上线。

──────────────────────────────────────

三、插件工作原理(你不需要额外代码)

dbresolver 在内部注册了一个 callbacks 链。当你后续写下

go 复制代码
db.First(&user, 1)          // 纯查询
db.Create(&order)           // 写入

它会自动根据 SQL 操作类别,把请求路由到:

Replicas(读池) → user 查询

Sources(写池) → order 插入

事务(db.Transaction(func(tx *gorm.DB) {...}))默认锁定在 Sources,保证读写一致性。

──────────────────────────────────────

四、连接池参数与日志

连接池大小、生命周期在 3 行里统一设置(对读库池生效;写库池由 dbresolver 使用默认值,也可再通过 Configure 调整):

go 复制代码
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)

成功后 logrus.Info("数据库连接成功") 会在控制台彩显,并写到每日分割的文件里。

──────────────────────────────────────

五、如何"一键切换"读写分离

  1. 本地/测试环境想省事------只留 db:、删掉或注释 db1:

    Empty() 返回 true

    • InitDB 不注册 dbresolver → 整个程序只使用单库

  2. 生产环境要上读写分离------补全 db1:

    • 下次启动时 Empty() 变 false

    • dbresolver 生效,写库指向 db1,读库指向 db

代码无需改动,只改 YAML 即可完成模式切换。

──────────────────────────────────────

六、重点回顾(记住 4 个名词即可)

  1. DB / DB1 :YAML 中两套连接参数,分别映射到 conf.DB 结构体
  2. Empty():用来检测写库是否配置
  3. dbresolver.Register:把 Sources(写库)和 Replicas(读库)注册进 GORM
  4. RandomPolicy:多读库时的负载均衡策略

如此,"读写分离"在项目中就靠这短短十几行实现,既灵活(配置即开关),又安全(配置缺失时自动回退单库)。

相关推荐
码小跳2 分钟前
软件无法连接MySql数据库
数据库·mysql
Code季风14 分钟前
深入比较 Gin 与 Beego:Go Web 框架的两大选择
开发语言·golang·go·gin·beego
Code季风20 分钟前
Gin 中间件详解与实践
学习·中间件·golang·go·gin
九班长21 分钟前
Golang服务端处理Unity 3D游戏地图与碰撞的详细实现
3d·unity·golang
晋阳十二夜6 小时前
【压力测试之_Jmeter链接Oracle数据库链接】
数据库·oracle·压力测试
GDAL7 小时前
Node.js v22.5+ 官方 SQLite 模块全解析:从入门到实战
数据库·sqlite·node.js
DCTANT8 小时前
【原创】国产化适配-全量迁移MySQL数据到OpenGauss数据库
java·数据库·spring boot·mysql·opengauss
AI、少年郎10 小时前
Oracle 进阶语法实战:从多维分析到数据清洗的深度应用(第四课)
数据库·oracle
赤橙红的黄10 小时前
自定义线程池-实现任务0丢失的处理策略
数据库·spring
DataGear11 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化