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:多读库时的负载均衡策略

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

相关推荐
倔强的石头_1 天前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou642 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤3 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区4 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1774 天前
《从零搭建NestJS项目》
数据库·typescript
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
加号35 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏5 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐5 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再5 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip