之前同事反馈 MySQL 主库压力越来越大,虽然主从同步早就已经有了,但升级 gorm 之后一直没有引入读写分离,便商量着加入读写分离减轻主库压力。如今已上线两月有余了,比较稳定,今天来分享一下利用 gorm 实现读写分离。
gorm 的读写分离是已扩展插件的形式实现的,即 dbresolver
配置文件
| db:separation: true
 master: "root:123456@tcp(127.0.0.1:3306)/a0001_chat?charset=utf8mb4&parseTime=True&loc=Local"
 slave:
 - "root:123456@tcp(127.0.0.1:3307)/a0001_chat?charset=utf8mb4&parseTime=True&loc=Local"
 - "root:123456@tcp(127.0.0.1:3308)/a0001_chat?charset=utf8mb4&parseTime=True&loc=Local"
 
 | 
主要代码
| package main
 import (
 "fmt"
 "time"
 
 "github.com/pkg/errors"
 "github.com/sirupsen/logrus"
 "github.com/spf13/viper"
 "gorm.io/driver/mysql"
 "gorm.io/gorm"
 "gorm.io/plugin/dbresolver"
 )
 
 var (
 MainDB *gorm.DB
 )
 
 func main() {
 viper.SetConfigName("config")
 viper.SetConfigType("yaml")
 viper.AddConfigPath(".")
 err := viper.ReadInConfig()
 if err != nil {
 panic(fmt.Errorf("Fatal error config file: %w \n", err))
 }
 MainDB, err = ConnectDB()
 if err != nil {
 panic(fmt.Errorf("Fatal MainDB config file: %w \n", err))
 }
 err = MainDB.Raw("select version()").Error
 if err != nil {
 logrus.Infof("err=%+v", err)
 return
 }
 logrus.Info("puresai")
 }
 
 func ConnectDB() (d *gorm.DB, err error) {
 if viper.GetBool("db.separation") {
 return ConnectRWDB()
 }
 dsn := viper.GetString("db.master")
 d, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
 if err != nil {
 return nil, errors.Wrap(err, "数据库连接失败")
 }
 db, err := d.DB()
 if err != nil {
 return nil, errors.Wrap(err, "获取数据库实例失败")
 }
 db.SetMaxIdleConns(10)
 db.SetConnMaxLifetime(time.Hour)
 return d, nil
 }
 
 func ConnectRWDB() (d *gorm.DB, err error) {
 logrus.Info("使用读写分离")
 dsn := viper.GetString("db.master")
 d, err = gorm.Open(mysql.New(mysql.Config{
 DSN: dsn,
 }))
 if err != nil {
 return nil, err
 }
 replicas := []gorm.Dialector{}
 for i, s := range viper.GetStringSlice("db.slave") {
 cfg := mysql.Config{
 DSN: s,
 }
 logrus.Infof("读写分离-%d-%s", i, s)
 replicas = append(replicas, mysql.New(cfg))
 }
 
 d.Use(
 dbresolver.Register(dbresolver.Config{
 Sources: []gorm.Dialector{mysql.New(mysql.Config{
 DSN: dsn,
 })},
 Replicas: replicas,
 Policy:   dbresolver.RandomPolicy{},
 }).
 SetMaxIdleConns(10).
 SetConnMaxLifetime(time.Hour).
 SetMaxOpenConns(200),
 )
 
 return d, nil
 }
 
 | 
主要代码就是:
| d.Use(dbresolver.Register(dbresolver.Config{
 Sources: []gorm.Dialector{mysql.New(mysql.Config{
 DSN: dsn,
 })},
 Replicas: replicas,
 Policy:   dbresolver.RandomPolicy{},
 }),
 )
 
 | 
more
这里只是做个demo,省略了业务代码,所以提醒一下
- 务必测试下具体业务
- 注意写后立即读的问题
- 注意db与缓存一致性问题
- 连接池和超时设置根据业务定义
参考