gorm读写分离

之前同事反馈 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与缓存一致性问题
  • 连接池和超时设置根据业务定义

参考


gorm读写分离
https://blog.puresai.com/2022/06/05/402/
作者
puresai
许可协议