从配置文件开始到实现数据库读写分离的整个详细的工作流程

从读取配置文件开始到完成数据库读写分离配置的整个工作流程。

核心目标: 应用程序启动时,读取 settings.yaml 中的数据库配置(包括一个写库 db1 和一个读库 db),然后使用这些配置初始化一个 GORM 数据库实例,该实例具备读写分离能力。

整个工作流程如下:

阶段一:定义配置结构和全局变量

  1. conf/enter.go - 定义配置蓝图

    • 作用:这个文件定义了应用程序所有配置项的结构。
    • 代码
      package conf
      
      type Config struct {
          System System `yaml:"system"` // 系统相关配置
          Log    Log    `yaml:"log"`    // 日志相关配置
          DB     DB     `yaml:"db"`     // 对应 settings.yaml "db:",预期作为读库
          DB1    DB     `yaml:"db1"`    // 对应 settings.yaml "db1:",预期作为写库
      }
      // (假设 System, Log, DB 这些子结构体也在 conf 包中其他文件定义,例如 conf_system.go, conf_log.go, conf_db.go)
      
    • 流程:它声明了一个 Config 类型,其中 DBDB1 字段都是 conf.DB 类型(这个类型在 conf/conf_db.go 中定义,包含了如 Host, Port, User, Password, DSN() 方法等)。yaml:"db"yaml:"db1" 标签指示了 YAML 解析器如何将 settings.yaml 中的键映射到这些字段。
  2. global/enter.go - 创建全局配置容器

    • 作用:定义全局变量,使得配置信息和初始化后的数据库实例可以在项目的任何地方被方便地访问。
    • 代码
      package global
      
      import (
          "blogx_server/conf"
          "gorm.io/gorm"
      )
      
      var Config *conf.Config // 全局变量,用于存储加载后的所有配置信息
      var DB *gorm.DB         // 全局变量,用于存储初始化后的 GORM 数据库操作实例
      
    • 流程var Config *conf.Config 创建了一个指向 conf.Config 结构体的指针。程序启动后,解析得到的配置数据将存放在这里。var DB *gorm.DB 则是为了存放最终配置好读写分离的数据库连接实例。

阶段二:加载配置文件

  1. core/init_conf.go - 读取并解析配置文件

    • 作用:负责从指定的 YAML 文件中读取内容,并将其解析(反序列化)到 conf.Config 结构体中。
    • 代码
      package core
      
      import (
          "blogx_server/conf"
          "blogx_server/flags" // 假设 flags 包处理命令行参数,如配置文件路径
          "fmt"
          "gopkg.in/yaml.v2"
          "os"
      )
      
      func ReadConf() (c *conf.Config) { // 返回一个指向 conf.Config 的指针
          // 1. 获取配置文件路径 (通常来自命令行参数或默认值)
          //    flags.FlagOptions.File 存储了配置文件名,如 "settings.yaml"
          byteData, err := os.ReadFile(flags.FlagOptions.File)
          if err != nil {
              panic(err) // 读取文件失败则恐慌
          }
      
          // 2. 初始化一个 conf.Config 结构体实例的指针
          c = new(conf.Config)
      
          // 3. 解析 YAML 数据
          //    将 byteData (文件内容) 解析到 c 指向的结构体中
          //    gopkg.in/yaml.v2 会根据字段的 yaml 标签进行映射
          err = yaml.Unmarshal(byteData, c)
          if err != nil {
              panic(fmt.Sprintf("yaml配置文件格式错误:%s", err)) // 解析失败则恐慌
          }
      
          fmt.Printf("读取配置文件 %s 成功\n", flags.FlagOptions.File)
          return c // 返回填充好数据的配置结构体指针
      }
      
    • 流程
      • ReadConf() 函数被调用(通常在 main.go 的早期)。
      • 它读取 flags.FlagOptions.File 指定的配置文件(即 settings.yaml)。
      • 使用 yaml.Unmarshal 将文件内容解析填充到新创建的 conf.Config 实例中。此时,settings.yamlsystem:, log:, db:, db1: 各个块的数据,会分别填充到 conf.Config 结构体实例对应的 System, Log, DB, DB1 字段中。
      • 函数返回这个填充好的 *conf.Config
  2. 主程序(例如 main.go,未提供但推断)- 将配置存入全局变量

    • 流程
      // 在 main.go 或类似的启动文件中
      // import "blogx_server/global"
      // import "blogx_server/core"
      // import "blogx_server/flags"
      
      func main() {
          flags.Parse() // 解析命令行参数,设置 flags.FlagOptions.File
          loadedConfig := core.ReadConf() // 调用 ReadConf 加载配置
          global.Config = loadedConfig   // 将加载的配置存入全局变量 global.Config
      
          // ... 接下来会调用 core.InitDB() ...
      }
      
    • 这样,global.Config 就持有了从 settings.yaml 解析出来的所有配置信息,包括 global.Config.DB (来自 “db:”) 和 global.Config.DB1 (来自 “db1:”)。

阶段三:初始化数据库并配置读写分离

  1. core/init_db.go - 初始化数据库连接并应用读写分离策略

    • 作用:使用加载到的数据库配置信息,初始化 GORM,并配置 dbresolver 插件以实现读写分离。
    • 代码(基于您最近一次修改后的版本):
      package core
      
      import (
          "blogx_server/global" // 用于访问全局配置 global.Config
          "fmt"
          "github.com/sirupsen/logrus" // 日志库
          "gorm.io/driver/mysql"       // GORM 的 MySQL 驱动
          "gorm.io/gorm"               // GORM 核心库
          "gorm.io/plugin/dbresolver"  // GORM 的读写分离插件
          "time"
      )
      
      func InitDB() *gorm.DB {
          // 1. 获取已加载的数据库配置
          //    dc 预期作为读库配置 (来自 settings.yaml "db:")
          //    dc1 预期作为写库配置 (来自 settings.yaml "db1:")
          dc := global.Config.DB
          dc1 := global.Config.DB1
      
          // 2. 连接到写数据库 (dc1)
          //    这是 GORM 主实例的初始连接。
          //    后续 dbresolver 会基于此实例进行读写分离的路由。
          db, err := gorm.Open(mysql.Open(dc1.DSN()), &gorm.Config{
              DisableForeignKeyConstraintWhenMigrating: true,
          })
          if err != nil {
              logrus.Fatalf("连接写数据库 (%s) 失败: %s", dc1.Host, err)
          }
          fmt.Println(db, err) // 调试信息
      
          // 3. 配置连接池
          sqlDB, _ := db.DB() // 错误已在上面处理或忽略
          sqlDB.SetMaxIdleConns(10)
          sqlDB.SetMaxOpenConns(100)
          sqlDB.SetConnMaxLifetime(time.Hour)
          logrus.Infof("写数据库 (%s) 连接成功", dc1.Host) // 确认写库连接
      
          // 4. 配置读写分离插件
          if !dc1.Empty() { // 确保写库配置 (dc1) 是有效的
              // 准备读写分离配置
              err = db.Use(dbresolver.Register(dbresolver.Config{
                  // 写库 (Sources): 使用 dc1 (来自 "db1:")
                  Sources:  []gorm.Dialector{mysql.Open(dc1.DSN())},
                  // 读库 (Replicas): 使用 dc (来自 "db:")
                  // 注意:如果 dc 是空的或无效的,这里的 mysql.Open(dc.DSN()) 会有问题。
                  // 在之前的版本中,我们加入了对 dc.Empty() 的检查来处理这种情况。
                  // 您当前版本直接使用了它,意味着您期望 dc 总是有效的,或者接受潜在的错误。
                  Replicas: []gorm.Dialector{mysql.Open(dc.DSN())},
                  Policy:   dbresolver.RandomPolicy{}, // 读负载均衡策略
              }))
              if err != nil {
                  logrus.Fatalf("读写配置错误 %s", err)
              }
              // 假设 dc.Host 也是有效的用于日志
              logrus.Infof("读写分离配置成功。写库: %s (来自 'db1'), 读库: %s (来自 'db')", dc1.Host, dc.Host)
          } else {
              // 如果写库配置 dc1 为空,则不配置读写分离,并发出警告
              logrus.Warnf("写库配置 (settings.yaml 的 'db1') 为空,未配置读写分离。所有操作将尝试使用该 (可能无效的) 连接。")
          }
      
          // 5. (可选但常见) 将配置好的 GORM 实例存入全局变量
          // global.DB = db // 如果您希望全局访问
      
          return db // 返回配置了读写分离的 GORM DB 实例
      }
      
    • 流程
      • InitDB() 函数被调用(通常在 main.go 中,在 global.Config 被赋值之后)。
      • 它从 global.Config 中分别获取 DB (赋给 dc,预期为读库) 和 DB1 (赋给 dc1,预期为写库) 的配置。
      • 初始连接:使用 dc1.DSN() (写库的连接字符串) 调用 gorm.Open,创建一个基础的 GORM db 实例。这个实例首先被配置为连接到写数据库。
      • 配置连接池:为这个基础连接设置连接池参数。
      • 配置 dbresolver
        • 检查写库配置 dc1 是否有效。
        • 如果有效,调用 db.Use(dbresolver.Register(...)) 来注册读写分离规则。
          • Sources: 设置为 mysql.Open(dc1.DSN()),意味着所有写操作(INSERT, UPDATE, DELETE)将通过 dc1 (即 settings.yamldb1:) 配置的数据库进行。
          • Replicas: 设置为 mysql.Open(dc.DSN()),意味着所有读操作(SELECT)将通过 dc (即 settings.yamldb:) 配置的数据库进行。
          • Policy: dbresolver.RandomPolicy{},如果将来有多个读库副本,会随机选择一个。
        • 记录相应的成功或失败日志。
      • 函数返回这个经过 dbresolver 增强的 *gorm.DB 实例。
  2. 主程序(例如 main.go)- 使用数据库实例

    • 流程
      // 在 main.go 或类似的启动文件中
      // ... (紧接之前的 main 函数流程)
      gormDBInstance := core.InitDB()
      global.DB = gormDBInstance // 将返回的 GORM 实例存入全局变量
      
      // 之后,项目中的其他部分就可以通过 global.DB 来执行数据库操作了。
      // GORM 会根据操作类型自动路由到读库或写库。
      // 例如:
      // var user User
      // global.DB.Where("name = ?", "jinzhu").First(&user) // 读操作,会走 Replicas (dc)
      // global.DB.Create(&User{Name: "dave", Age: 28})    // 写操作,会走 Sources (dc1)
      

总结整个流程的关键点:

  • 配置分离:配置信息 (settings.yaml) 与代码逻辑分离。
  • 结构化配置:使用 Go 结构体 (conf.Config, conf.DB) 来清晰地表示和管理配置数据。
  • 全局访问:通过全局变量 (global.Config, global.DB) 使得配置和数据库实例易于在项目中共享。
  • 模块化初始化core.ReadConf() 负责加载配置,core.InitDB() 负责数据库初始化和读写分离配置。
  • GORM dbresolver 插件:这是实现读写分离的核心技术,它透明地将GORM的读写操作路由到不同的数据库连接。
  • 明确的约定:代码中通过变量名 (dc vs dc1) 和 dbresolverSources/Replicas 配置,建立了 settings.yamldb: 作为读库、db1: 作为写库的约定。

这个流程展示了一个典型的 Go 应用中如何管理配置并实现如数据库读写分离这样的高级功能。

你可能感兴趣的:(BlogX项目笔记,数据库,网络,golang,gin)