You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Golang中适配AWS RDS MySQL主从复制实现读写分离?

针对你的AWS RDS一主两从架构和Golang读写分离需求,我给你几个实用的落地方案,你可以根据项目情况选择:

方案1:利用AWS RDS只读端点(最省心)

AWS RDS本身提供了只读端点,这个端点会自动把读请求负载均衡到所有可用的从库上,还能自动处理从库的故障转移——当某个从库挂了,RDS会自动将其从负载池中移除,完全不需要你自己维护逻辑。

代码实现

import (
    "database/sql"
    "time"
    _ "github.com/go-sql-driver/mysql"
)

var (
    masterDB *sql.DB // 主库连接,负责写操作
    readDB   *sql.DB // 只读端点连接,负责读操作
)

func initDB() error {
    // 主库DSN:替换成你的RDS主库端点
    masterDSN := "root:password@tcp(your-master-rds-endpoint:3306)/test"
    var err error
    masterDB, err = sql.Open("mysql", masterDSN)
    if err != nil {
        return err
    }
    // 配置连接池参数,根据业务量调整
    masterDB.SetMaxOpenConns(20)
    masterDB.SetMaxIdleConns(10)
    masterDB.SetConnMaxLifetime(1 * time.Hour)

    // RDS只读端点DSN:替换成你的只读端点
    readDSN := "root:password@tcp(your-read-replica-rds-endpoint:3306)/test"
    readDB, err = sql.Open("mysql", readDSN)
    if err != nil {
        return err
    }
    // 同样配置读连接池参数
    readDB.SetMaxOpenConns(20)
    readDB.SetMaxIdleConns(10)
    readDB.SetConnMaxLifetime(1 * time.Hour)

    // 验证连接可用性
    if err := masterDB.Ping(); err != nil {
        return err
    }
    if err := readDB.Ping(); err != nil {
        return err
    }
    return nil
}

// 封装写操作:强制走主库
func executeWrite(query string, args ...interface{}) (sql.Result, error) {
    return masterDB.Exec(query, args...)
}

// 封装读操作:走只读端点(自动负载均衡到从库)
func executeRead(query string, args ...interface{}) (*sql.Rows, error) {
    return readDB.Query(query, args...)
}

// 针对单行查询的封装
func executeReadRow(query string, args ...interface{}) *sql.Row {
    return readDB.QueryRow(query, args...)
}

优缺点

  • ✅ 优点:零额外维护成本,RDS自动处理从库负载均衡、故障转移;代码改动最小,只需要新增一个只读连接。
  • ❌ 缺点:依赖AWS RDS特性,如果未来迁移到其他云厂商,需要调整方案。

方案2:手动封装读写连接池(轻量可控)

如果不想依赖云厂商特性,你可以自己维护主库和多个从库的连接池,手动实现读请求的负载均衡逻辑(比如随机、轮询)。

代码实现

import (
    "database/sql"
    "math/rand"
    "time"
    _ "github.com/go-sql-driver/mysql"
)

var (
    masterDB  *sql.DB
    slaveDBs  []*sql.DB
    randSeed  = rand.New(rand.NewSource(time.Now().UnixNano()))
)

func initDB() error {
    // 初始化主库连接
    masterDSN := "root:password@tcp(your-master-rds-endpoint:3306)/test"
    var err error
    masterDB, err = sql.Open("mysql", masterDSN)
    if err != nil {
        return err
    }
    masterDB.SetMaxOpenConns(20)
    masterDB.SetMaxIdleConns(10)
    masterDB.SetConnMaxLifetime(1 * time.Hour)

    // 初始化所有从库连接
    slaveDSNs := []string{
        "root:password@tcp(your-slave1-rds-endpoint:3306)/test",
        "root:password@tcp(your-slave2-rds-endpoint:3306)/test",
    }
    for _, dsn := range slaveDSNs {
        slaveDB, err := sql.Open("mysql", dsn)
        if err != nil {
            return err
        }
        slaveDB.SetMaxOpenConns(20)
        slaveDB.SetMaxIdleConns(10)
        slaveDB.SetConnMaxLifetime(1 * time.Hour)
        slaveDBs = append(slaveDBs, slaveDB)
    }

    // 验证所有连接
    if err := masterDB.Ping(); err != nil {
        return err
    }
    for _, db := range slaveDBs {
        if err := db.Ping(); err != nil {
            return err
        }
    }
    return nil
}

// 获取读连接:随机选择从库,无从库时 fallback 到主库
func getReadDB() *sql.DB {
    if len(slaveDBs) == 0 {
        return masterDB
    }
    return slaveDBs[randSeed.Intn(len(slaveDBs))]
}

// 获取写连接:固定使用主库
func getWriteDB() *sql.DB {
    return masterDB
}

使用示例

// 写操作:插入用户
func insertUser(name string) error {
    _, err := getWriteDB().Exec("INSERT INTO users(name) VALUES(?)", name)
    return err
}

// 读操作:查询用户
func getUser(id int) (string, error) {
    var name string
    err := getReadDB().QueryRow("SELECT name FROM users WHERE id=?", id).Scan(&name)
    return name, err
}

优缺点

  • ✅ 优点:不依赖任何第三方库或云厂商特性,完全可控;可以自定义负载均衡策略(比如轮询、加权随机)。
  • ❌ 缺点:需要自己实现故障检测和切换逻辑(比如某个从库挂了要从列表中移除),维护成本稍高。

方案3:使用ORM框架(如GORM)开箱即用

如果你的项目已经在使用ORM,比如GORM v2,它内置了读写分离的支持,配置起来非常简单,还能自动处理负载均衡和故障切换。

代码实现

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "math/rand"
)

func initGORM() (*gorm.DB, error) {
    // 主库DSN
    masterDSN := "root:password@tcp(your-master-rds-endpoint:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    // 从库DSNs
    slaveDSNs := []string{
        "root:password@tcp(your-slave1-rds-endpoint:3306)/test?charset=utf8mb4&parseTime=True&loc=Local",
        "root:password@tcp(your-slave2-rds-endpoint:3306)/test?charset=utf8mb4&parseTime=True&loc=Local",
    }

    // 初始化主库连接
    masterDB, err := gorm.Open(mysql.Open(masterDSN), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    // 初始化从库连接并配置读写分离
    var replicas []*gorm.DB
    for _, dsn := range slaveDSNs {
        replica, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            return nil, err
        }
        replicas = append(replicas, replica)
    }

    // 启用读写分离插件,配置随机负载均衡策略
    err = masterDB.Use(&gorm.Replica{
        Replicas: replicas,
        Policy: func(_ *gorm.DB) (*gorm.DB, error) {
            return replicas[rand.Intn(len(replicas))], nil
        },
    })
    if err != nil {
        return nil, err
    }

    return masterDB, nil
}

使用示例

type User struct {
    ID   uint
    Name string
}

// 写操作:自动走主库
func createUser(db *gorm.DB, user *User) error {
    return db.Create(user).Error
}

// 读操作:自动走从库
func getUser(db *gorm.DB, id uint) (User, error) {
    var user User
    err := db.First(&user, id).Error
    return user, err
}

优缺点

  • ✅ 优点:开箱即用,无需自己封装连接池和负载均衡;支持复杂的查询场景,自动区分读写操作。
  • ❌ 缺点:如果之前用原生SQL,需要调整代码适配ORM;引入了第三方依赖。

内容的提问来源于stack exchange,提问作者Vish K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:25:05