GO语言使用gorm的dbresolver插件实现数据库读写分离
GO语言使用gorm的dbresolver插件实现数据库读写分离
settings.yaml文件中读库跟写库的配置信息:db:读库,db1:写库
db:user: rootpassword: 123456host: 127.0.0.1port: 3306db: blogxdebug: falsesource: mysql
db1:user: rootpassword: 123456host: 127.0.0.1port: 3306db: testdebug: falsesource: mysql
conf/enter.go
type Config struct {System System `yaml:"system"`Log Log `yaml:"log"`DB DB `yaml:"db"` //读库DB1 DB `yaml:"db1"` //写库
}
conf/conf_db.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”
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 处代码:
- 配置层
•settings.yaml
里的db:
(读库)和db1:
(写库)
•conf/conf_db.go
里的DB
结构体 +Empty()
方法 - 启动层
•core/init_db.go
中利用 GORM 的dbresolver
插件,把两份配置注册成 “Sources / Replicas” - 业务层
• 业务代码照常用db.Create / db.Find…
,GORM 自动把写请求导向写库,把读请求导向读库。
下面循序渐进地把每一步拆开讲。
──────────────────────────────────────
一、YAML + 结构体:怎样描述两套数据库
- settings.yaml(缩写示例)
db: # 读库user: rootpassword: 123456host: 127.0.0.1port: 3306db: blogxdebug: falsesource: mysqldb1: # 写库user: rootpassword: 123456host: 192.168.1.10port: 3306db: blogxdebug: falsesource: mysql
- conf/enter.go
type Config struct {System System `yaml:"system"`Log Log `yaml:"log"`DB DB `yaml:"db"` // 读DB1 DB `yaml:"db1"` // 写
}
- conf/conf_db.go
•DB.DSN()
把账号/主机/库名拼成 MySQL 连接串;
•DB.Empty()
判断该配置是否完全为空(四项都没填);后面用于“若用户没配写库就别启动读写分离”。
──────────────────────────────────────
二、InitDB:用 dbresolver 把“读 / 写”连起来
核心流程位于 core/init_db.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{}, // 读负载均衡策略}))...
}
细节说明:
-
先用读库 DSN 建立基础连接——这样即便只配一套库,也能正常跑起来。
-
判断写库是否留空——
dc1.Empty()
会返回 true/false:
• 如果用户没有在 YAML 里填db1:
,代码就不会注册dbresolver
,整个项目退化成“单库”模式。
• 如果配了,才执行db.Use(...)
,将两套连接池交给插件管理。 -
Sources / Replicas 的含义
•Sources
:主库(写库)。GORM 会把所有会写入数据的操作(Create、Update、Delete、Exec、事务)发送到这里。
•Replicas
:从库(读库)。所有纯查询(First、Find、Scan、Raw SQL 查询)会走这里。 -
Policy:当有多台从库时负责负载均衡。此处用
RandomPolicy
,即随机挑一台;GORM 还内置RoundRobinPolicy
等策略。 -
错误处理:如果
db.Use
返回 err,立即logrus.Fatalf(...)
终止启动,避免带错配置上线。
──────────────────────────────────────
三、插件工作原理(你不需要额外代码)
dbresolver
在内部注册了一个 callbacks
链。当你后续写下
db.First(&user, 1) // 纯查询
db.Create(&order) // 写入
它会自动根据 SQL 操作类别,把请求路由到:
• Replicas
(读池) → user
查询
• Sources
(写池) → order
插入
事务(db.Transaction(func(tx *gorm.DB) {...})
)默认锁定在 Sources
,保证读写一致性。
──────────────────────────────────────
四、连接池参数与日志
连接池大小、生命周期在 3 行里统一设置(对读库池生效;写库池由 dbresolver 使用默认值,也可再通过 Configure
调整):
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
成功后 logrus.Info("数据库连接成功")
会在控制台彩显,并写到每日分割的文件里。
──────────────────────────────────────
五、如何“一键切换”读写分离
-
本地/测试环境想省事——只留
db:
、删掉或注释db1:
:
•Empty()
返回 true
• InitDB 不注册 dbresolver → 整个程序只使用单库 -
生产环境要上读写分离——补全
db1:
:
• 下次启动时Empty()
变 false
• dbresolver 生效,写库指向db1
,读库指向db
代码无需改动,只改 YAML 即可完成模式切换。
──────────────────────────────────────
六、重点回顾(记住 4 个名词即可)
- DB / DB1:YAML 中两套连接参数,分别映射到
conf.DB
结构体 - Empty():用来检测写库是否配置
- dbresolver.Register:把 Sources(写库)和 Replicas(读库)注册进 GORM
- RandomPolicy:多读库时的负载均衡策略
如此,“读写分离”在项目中就靠这短短十几行实现,既灵活(配置即开关),又安全(配置缺失时自动回退单库)。