配置管理 原创
在应用程序的运行生命周期中,应用配置的读取和更新可以直接改变应用程序,比如:
- 在启动时,可以读取配置进行一系列初始化操作,如数据库的配置信息
- 在运行时,可以通过监听文件的方式热加载配置,达到灰度的效果
本项目主要使用 viper 和 cast 来实现配置管理。其中:
- viper 主要用来管理配置
- cast 主要用来类型转换
1、安装依赖包
$ go get github.com/spf13/viper
$ go get github.com/spf13/cast
2、添加配置文件
在项目的configs
目录下创建config.yaml
文件,写入以下配置:
Server:
RunMode: debug
Port: 8080
ReadTimeout: 3600
WriteTimeout: 3600
IdleTimeout: 300
在配置文件中,我们编写的配置详情如下:
- Server:服务配置,设置运行 WEB 服务所需的信息
3、编写组件
编写完配置之后,我们需要读取配置,这时候就要对读取配置的行为进行封装。
首先,在 pkg/setting
目录中新建 setting.go
文件,写入如下代码:
package setting
import "github.com/spf13/viper"
type Setting struct {
vp *viper.Viper
}
// NewSetting 初始化配置文件的基础属性
func NewSetting() (*Setting, error) {
vp := viper.New()
vp.SetConfigName("config")
vp.AddConfigPath("configs")
vp.SetConfigType("yaml")
if err := vp.ReadInConfig(); err != nil {
return nil, err
}
return &Setting{
vp: vp,
}, nil
}
在上述代码中,我们编写了 NewSetting 方法,用于初始化本项目配置的基础属性,即设定配置文件的名称为 config、配置类型为 yaml,并且设置其配置路径为相对路径 configs/,以确保在项目目录下能够成功启动编写组件。
接下来再在 pkg/setting
目录下创建 section.go
文件,用于声明配置属性的结构体,如下:
package setting
import "time"
// ServerSettingS 服务端配置结构体
type ServerSettingS struct {
RunMode string
Port string
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
// ReadSection 读取配置信息
// k: 模块名
// v: 配置的结构体
// example:
// ServerSettingS.ReadSection("Server",&ServerSettingS)
func (s *Setting) ReadSection(k string, v interface{}) error {
if err := s.vp.UnmarshalKey(k, v); err != nil {
return err
}
return nil
}
由于数据库和应用配置现在没有使用,所以现在只配置了服务配置,其他配置在使用的时候再慢慢添加。
4、添加全局变量
仅读取文件的配置信息是不够的,我们还需将配置信息和应用程序关联起来,这样才能使用它。下面在项目目录的global
目录下新建setting.go
文件,写入如下代码:
package global
import "github.com/joker-bai/hawkeye/pkg/setting"
var (
Setting *setting.ServerSettingS
)
5、初始化配置
上面我们已经做完了关于配置的前置准备,我们的最终需求是要在服务中使用到配置,所以现在需要在服务启动的时候初始化配置信息,以便能正常读取配置。
在initialize
包中新增setting.go
文件,写入以下内容:
package initialize
import (
"github.com/joker-bai/hawkeye/global"
"github.com/joker-bai/hawkeye/pkg/setting"
)
// SetupSetting 初始化配置
func SetupSetting() error {
s, err := setting.NewSetting()
if err != nil {
return err
}
// 加载配置
if err := s.ReadSection("Server", &global.ServerSetting); err != nil {
return err
}
return nil
}
该方法主要是实例化配置文件,将配置信息和实例关联起来。
然后将初始化配置的操作写入 main.go
入口函数中,并将服务配置改成从配置文件读取,如下:
package main
import (
"log"
"net/http"
"time"
"github.com/joker-bai/hawkeye/global"
"github.com/joker-bai/hawkeye/initialize"
)
func init() {
// 初始化配置
if err := initialize.SetupSetting(); err != nil {
log.Fatalf("Failed to init setting, error: %s", err)
}
}
func main() {
// 初始化
engine := initialize.NewEngine()
server := &http.Server{
Addr: ":" + global.ServerSetting.Port,
WriteTimeout: time.Second * global.ServerSetting.WriteTimeout,
ReadTimeout: time.Second * global.ServerSetting.ReadTimeout,
IdleTimeout: time.Second * global.ServerSetting.IdleTimeout,
Handler: engine,
}
// 运行服务
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start http server, error: %s", err)
}
}
这里,我们引入了 init
方法,该方法的逻辑如下:
- 如果一个包定义了 init 函数,Go 运行时会负责在该包初始化时调用它的 init 函数;
- init 不能被显式调用 ,否则会在编译期间报错;
- 多个包的情况,在初始化该包时,Go 运行时会按照一定的次序逐一顺序地调用该包的 init 函数;
- 每个 init 函数在整个 Go 程序生命周期内仅会被执行一次;
- 一般来说,先被传递给 Go 编译器的源文件中的 init 函数先被执行(main.go 作为起点);
- 同一个源文件中的多个 init 函数按声明顺序依次执行。
6、测试一下
上面已经把配置相关的信息处理完毕,现在来验证一下。
首先,看服务启动是否正常,如下表示正常:
再次,用apifox
测试业务是否正常,如下表示正常:
7、代码版本
本节开发完,为代码做个标记,如下:
$ git add .
$ git commit -m "add config manage"