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

主数据库宕机时备份库故障转移实现方案咨询(附Go代码)

数据库故障切换的Go代码方案咨询

现有系统流程代码

func main() {
    // conn 是数据库连接池
    conn := getConnection()
    router := newRouter(conn)
    err := http.ListenAndServe(":3333", router)
}

func newConnection() *sql.DB {
    // 模拟具备故障转移功能的连接创建
    // 如果数据库A宕机,则连接到数据库B
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/bookstore")
    if err != nil {
        log.Fatal(err)
    }
    return db
}

func newRouter(db *sql.DB) http.Handler {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
        // 在处理器内操作数据库
        if err != nil {
            // 此处需要切换到备份连接
        }
    })
    return r
}

该代码结构是"连接池封装"模式的修改版。主函数逻辑:从getConnection()获取数据库连接池,传入newRouter生成HTTP处理器,最后启动HTTP服务。

当前问题

当主数据库宕机时,处理器捕获错误后需要切换到备份数据库,但连接池由主函数传入,更新连接池需要修改处理器甚至重启HTTP服务,无法平滑切换。

临时解决方案

初始化时创建主、备两个连接池,同时传入路由器,主池故障时直接使用备池:

func main() {
    // conn 是主连接池
    conn := getConnection()
    backupConn := getBackupConnection()
    router := newRouter(conn, backupConn)
    err := http.ListenAndServe(":3333", router)
}

func newRouter(db *sql.DB, backupDb *sql.DB) http.Handler {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
        // 使用主池操作数据库
        if err != nil {
            // 切换到备池操作
            backupDb.Exec()
        }
    })
    return r
}

请问该方案是否属于最佳实践?还有其他可行的解决方案吗?


回答

临时方案的优缺点

优点

  • 实现简单:不需要复杂的连接池管理逻辑,直接在错误分支切换到备池即可
  • 无服务重启:备池提前初始化,故障时直接切换,无需重启HTTP服务

缺点

  • 代码侵入性高:每个数据库操作的错误分支都要手动处理备池切换,容易遗漏
  • 状态不同步:主备库的数据一致性需要依赖数据库自身的同步机制,代码层面无法保证
  • 资源浪费:备池长期处于闲置状态,占用额外的数据库连接资源
  • 无法自动切回:主库恢复后,需要手动修改代码或配置切回主池,无法自动感知

更优的解决方案

1. 封装连接池切换逻辑到自定义DB层

把主备切换的逻辑封装成一个自定义的DB结构体,对外暴露和*sql.DB一致的方法(如Exec、Query等),内部自动处理主备切换:

type FailoverDB struct {
    primary *sql.DB
    standby *sql.DB
    current *sql.DB
    mu      sync.RWMutex
}

func NewFailoverDB(primaryDSN, standbyDSN string) (*FailoverDB, error) {
    primary, err := sql.Open("postgres", primaryDSN)
    if err != nil {
        return nil, err
    }
    standby, err := sql.Open("postgres", standbyDSN)
    if err != nil {
        primary.Close()
        return nil, err
    }
    // 初始使用主池
    return &FailoverDB{
        primary: primary,
        standby: standby,
        current: primary,
    }, nil
}

func (f *FailoverDB) Exec(query string, args ...interface{}) (sql.Result, error) {
    f.mu.RLock()
    db := f.current
    f.mu.RUnlock()

    res, err := db.Exec(query, args...)
    if err != nil && isConnectionError(err) {
        // 检测到连接错误,尝试切换到备池
        f.mu.Lock()
        f.current = f.standby
        f.mu.Unlock()
        // 重试一次备池
        return f.standby.Exec(query, args...)
    }
    return res, err
}

// 实现其他需要的方法如Query、QueryRow等

然后在主函数中初始化这个自定义DB,传入路由器:

func main() {
    db, err := NewFailoverDB(primaryDSN, standbyDSN)
    if err != nil {
        log.Fatal(err)
    }
    router := newRouter(db)
    http.ListenAndServe(":3333", router)
}

优势:

  • 对业务代码无侵入:处理器只需要调用封装后的方法,无需关心主备切换逻辑
  • 可扩展性强:可以添加自动检测主库恢复、切回主池的逻辑
  • 统一管理:所有切换逻辑集中在一处,便于维护

2. 使用数据库驱动自带的故障转移功能

部分PostgreSQL驱动支持配置多个连接地址,驱动内部自动处理故障转移和负载均衡。例如使用pgxpool:

func newConnection() *pgxpool.Pool {
    cfg, err := pgxpool.ParseConfig("postgres://user:pass@localhost,backup-host/bookstore")
    if err != nil {
        log.Fatal(err)
    }
    // 配置故障转移策略
    cfg.ConnConfig.FallbackApplicationName = "bookstore-failover"
    pool, err := pgxpool.NewWithConfig(context.Background(), cfg)
    if err != nil {
        log.Fatal(err)
    }
    return pool
}

优势:

  • 无需自己实现切换逻辑:驱动层面已经处理了故障转移
  • 成熟稳定:驱动的故障转移逻辑经过充分测试,可靠性更高
  • 配置简单:只需要在DSN中添加多个地址即可

3. 使用外部代理实现数据库高可用

通过中间件实现数据库的自动故障转移,应用层只需要连接到代理地址,无需关心后端主备切换。
优势:

  • 应用层完全无感知:代码不需要做任何修改,由代理负责主备切换和负载均衡
  • 统一管理:所有数据库实例的高可用由代理统一维护
  • 支持更多高级功能:如读写分离、连接池管理等

内容的提问来源于stack exchange,提问作者Jeremy Kenn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 15:33:13