DBResolver 为 GORM 提供了多个数据库支持,支持以下功能:

  • 支持多个 sources、replicas
  • 读写分离
  • 根据工作表、struct 自动切换连接
  • 手动切换连接
  • Sources/Replicas 负载均衡
  • 适用于原生 SQL
  • Transaction

https://github.com/go-gorm/dbresolver

用法

  1. import (
  2. "gorm.io/gorm"
  3. "gorm.io/plugin/dbresolver"
  4. "gorm.io/driver/mysql"
  5. )
  6. db, err := gorm.Open(mysql.Open("db1_dsn"), &gorm.Config{})
  7. db.Use(dbresolver.Register(dbresolver.Config{
  8. // `db2` 作为 sources,`db3`、`db4` 作为 replicas
  9. Sources: []gorm.Dialector{mysql.Open("db2_dsn")},
  10. Replicas: []gorm.Dialector{mysql.Open("db3_dsn"), mysql.Open("db4_dsn")},
  11. // sources/replicas 负载均衡策略
  12. Policy: dbresolver.RandomPolicy{},
  13. }).Register(dbresolver.Config{
  14. // `db1` 作为 sources(DB 的默认连接),对于 `User`、`Address` 使用 `db5` 作为 replicas
  15. Replicas: []gorm.Dialector{mysql.Open("db5_dsn")},
  16. }, &User{}, &Address{}).Register(dbresolver.Config{
  17. // `db6`、`db7` 作为 sources,对于 `orders`、`Product` 使用 `db8` 作为 replicas
  18. Sources: []gorm.Dialector{mysql.Open("db6_dsn"), mysql.Open("db7_dsn")},
  19. Replicas: []gorm.Dialector{mysql.Open("db8_dsn")},
  20. }, "orders", &Product{}, "secondary"))

Automatic connection switching

DBResolver will automatically switch connection based on the working table/struct

For RAW SQL, DBResolver will extract the table name from the SQL to match the resolver, and will use sources unless the SQL begins with SELECT (excepts SELECT... FOR UPDATE), for example:

  1. // `User` Resolver 示例
  2. db.Table("users").Rows() // replicas `db5`
  3. db.Model(&User{}).Find(&AdvancedUser{}) // replicas `db5`
  4. db.Exec("update users set name = ?", "jinzhu") // sources `db1`
  5. db.Raw("select name from users").Row().Scan(&name) // replicas `db5`
  6. db.Create(&user) // sources `db1`
  7. db.Delete(&User{}, "name = ?", "jinzhu") // sources `db1`
  8. db.Table("users").Update("name", "jinzhu") // sources `db1`
  9. // Global Resolver 示例
  10. db.Find(&Pet{}) // replicas `db3`/`db4`
  11. db.Save(&Pet{}) // sources `db2`
  12. // Orders Resolver 示例
  13. db.Find(&Order{}) // replicas `db8`
  14. db.Table("orders").Find(&Report{}) // replicas `db8`

Read/Write Splitting

Read/Write splitting with DBResolver based on the current used GORM callbacks.

For Query, Row callback, will use replicas unless Write mode specified For Raw callback, statements are considered read-only and will use replicas if the SQL starts with SELECT

Manual connection switching

  1. // 使用 Write 模式:从 sources db `db1` 读取 user
  2. db.Clauses(dbresolver.Write).First(&user)
  3. // 指定 Resolver:从 `secondary` 的 replicas db `db8` 读取 user
  4. db.Clauses(dbresolver.Use("secondary")).First(&user)
  5. // 指定 Resolver 和 Write 模式:从 `secondary` 的 sources db `db6` 或 `db7` 读取 user
  6. db.Clauses(dbresolver.Use("secondary"), dbresolver.Write).First(&user)

Transaction

When using transaction, DBResolver will keep using the transaction and won’t switch to sources/replicas based on configuration

But you can specifies which DB to use before starting a transaction, for example:

  1. // Start transaction based on default replicas db
  2. tx := DB.Clauses(dbresolver.Read).Begin()
  3. // Start transaction based on default sources db
  4. tx := DB.Clauses(dbresolver.Write).Begin()
  5. // Start transaction based on `secondary`'s sources
  6. tx := DB.Clauses(dbresolver.Use("secondary"), dbresolver.Write).Begin()

负载均衡

GORM supports load balancing sources/replicas based on policy, the policy should be a struct implements following interface:

  1. type Policy interface {
  2. Resolve([]gorm.ConnPool) gorm.ConnPool
  3. }

Currently only the RandomPolicy implemented and it is the default option if no other policy specified.

连接池

  1. db.Use(
  2. dbresolver.Register(dbresolver.Config{ /* xxx */ }).
  3. SetConnMaxIdleTime(time.Hour).
  4. SetConnMaxLifetime(24 * time.Hour).
  5. SetMaxIdleConns(100).
  6. SetMaxOpenConns(200)
  7. )