Skip to content

25|配置管理

go-gateway 运行时需要知道监听端口、数据库地址、Redis 地址、JWT 密钥等参数。这些参数不应该硬编码在代码里,否则每次环境切换(开发 → 测试 → 生产)都要重新编译。配置管理的思路是:把可变参数抽到配置文件或环境变量里,程序启动时读取,运行期间按需要热加载。

一、环境变量

最基础的配置方式是环境变量,适合容器化和云原生部署:

go
package main

import (
	"os"
	"strconv"
)

type Config struct {
	ProxyAddr  string
	AdminAddr  string
	DBPath     string
	RedisAddr  string
	JWTSecret  string
}

func loadFromEnv() Config {
	return Config{
		ProxyAddr: getEnv("GATEWAY_PROXY_ADDR", ":8080"),
		AdminAddr: getEnv("GATEWAY_ADMIN_ADDR", ":8081"),
		DBPath:    getEnv("GATEWAY_DB_PATH", "./gateway.db"),
		RedisAddr: getEnv("GATEWAY_REDIS_ADDR", "127.0.0.1:6379"),
		JWTSecret: getEnv("GATEWAY_JWT_SECRET", "change-me"),
	}
}

func getEnv(key, fallback string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return fallback
}

环境变量的优点是简单、不依赖文件、和 Docker/K8s 配合自然。缺点是没有结构、不支持嵌套配置、修改后需要重启进程。

二、配置文件

复杂配置用 YAML 或 JSON 文件管理。Go 生态里 viper 是最流行的配置库,支持多格式、多来源、热加载:

bash
go get github.com/spf13/viper

config.yaml

yaml
server:
  proxy_addr: ":8080"
  admin_addr: ":8081"

database:
  driver: sqlite3
  dsn: ./gateway.db

redis:
  addr: 127.0.0.1:6379
  db: 0

jwt:
  secret: change-me
  expire_hours: 24

读取:

go
import "github.com/spf13/viper"

func loadConfig() error {
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath(".")
	viper.AddConfigPath("/etc/go-gateway/")

	if err := viper.ReadInConfig(); err != nil {
		return err
	}

	proxyAddr := viper.GetString("server.proxy_addr")
	jwtSecret := viper.GetString("jwt.secret")
	// ...
	return nil
}

三、多环境配置

开发、测试、生产环境的配置不同,用文件区分:

text
configs/
├── config.yaml          # 默认配置
├── config.dev.yaml      # 开发环境覆盖
└── config.prod.yaml     # 生产环境覆盖

通过环境变量选择配置文件:

go
env := getEnv("GATEWAY_ENV", "dev")
viper.SetConfigName("config." + env)
viper.MergeInConfig() // 合并覆盖

viper 的读取优先级(高到低):

  1. 显式 viper.Set 的值
  2. 命令行参数
  3. 环境变量
  4. 配置文件
  5. 默认值

四、配置热加载

网关运行期间修改配置后,不希望重启进程。viper 支持监听配置文件变化:

go
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("config file changed:", e.Name)
	// 重新加载路由表、调整连接池等
})

热加载不是万能的。监听端口、数据库连接串这类配置变更后,需要显式重建相关资源。配置变更事件里只通知"配置变了",具体的资源重建逻辑由业务代码处理。

五、敏感配置

JWT 密钥、数据库密码不能提交到代码仓库。处理方式:

  • 通过环境变量注入
  • 用 Docker Secrets / K8s Secrets
  • 开发环境用 .env 文件(加入 .gitignore
go
viper.BindEnv("jwt.secret", "GATEWAY_JWT_SECRET")

六、常见错误

配置和代码不同步

代码里读取 config.GetString("redis.add"),配置文件里写的是 redis.addr——拼写错误导致读到空值,Redis 连接失败。配置字段名变更时,要同步更新所有读取点。

默认值在生产环境生效

go
jwtSecret := getEnv("GATEWAY_JWT_SECRET", "change-me")

生产环境忘了设置 GATEWAY_JWT_SECRET,程序用默认的 change-me 启动,安全性形同虚设。启动时检查关键配置是否已设置,未设置则 panic 或退出。

热加载未处理并发

配置变更时,多个 goroutine 可能同时读取和写入配置。用 sync.RWMutex 保护配置对象,或者使用 viper 的原子读取(viper 内部已做并发安全处理)。