Skip to content

Day 5:配置中心+热更新——模型换了不用重启

今天做什么

目前模型配置是硬编码的。想加一个新模型?改代码、重新编译、重新部署。今天引入配置中心 + 热更新——改配置文件,网关自动生效。

代码

go
package main

import (
    "encoding/json"
    "os"
    "sync"
    "sync/atomic"
    "time"

    "github.com/fsnotify/fsnotify"
)

// ===== 配置定义 =====
type GatewayConfig struct {
    Models []ModelConfig `json:"models"`
    RateLimit RateLimitConfig `json:"rate_limit"`
    CircuitBreaker CircuitBreakerConfig `json:"circuit_breaker"`
}

type ModelConfig struct {
    Name   string  `json:"name"`
    URL    string  `json:"url"`
    APIKey string  `json:"api_key,omitempty"`
    Cost   float64 `json:"cost"`
    Priority int   `json:"priority"` // 优先级,越小越优先
}

// ===== 热更新配置管理器 =====
type ConfigManager struct {
    config atomic.Value // *GatewayConfig
    mu     sync.RWMutex
    path   string
}

func NewConfigManager(path string) (*ConfigManager, error) {
    cm := &ConfigManager{path: path}

    // 首次加载
    if err := cm.load(); err != nil {
        return nil, err
    }

    // 监听文件变化
    go cm.watch()

    return cm, nil
}

func (cm *ConfigManager) load() error {
    data, err := os.ReadFile(cm.path)
    if err != nil {
        return err
    }

    var cfg GatewayConfig
    if err := json.Unmarshal(data, &cfg); err != nil {
        return err
    }

    cm.config.Store(&cfg)
    log.Printf("✅ 配置已加载:%d 个模型", len(cfg.Models))
    return nil
}

func (cm *ConfigManager) Get() *GatewayConfig {
    return cm.config.Load().(*GatewayConfig)
}

// 监听配置文件变化
func (cm *ConfigManager) watch() {
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    watcher.Add(cm.path)

    // 防抖:1秒内的多次变化合并
    var debounceTimer *time.Timer

    for {
        select {
        case event := <-watcher.Events:
            if event.Op&fsnotify.Write == fsnotify.Write {
                if debounceTimer != nil {
                    debounceTimer.Stop()
                }
                debounceTimer = time.AfterFunc(1*time.Second, func() {
                    if err := cm.load(); err != nil {
                        log.Printf("❌ 配置加载失败: %v", err)
                    } else {
                        log.Println("🔄 配置已热更新")
                    }
                })
            }
        }
    }
}

// ===== 基于配置的路由 =====
func (g *Gateway) buildRouter() *ModelRouter {
    cfg := g.configManager.Get()

    router := NewRouter()
    for _, m := range cfg.Models {
        router.AddModel(m.Name, &ModelEndpoint{
            URL:    m.URL,
            APIKey: os.ExpandEnv(m.APIKey), // 支持 ${ENV_VAR}
            Cost:   m.Cost,
        })
    }

    // 按优先级排序路由
    router.SortByPriority()
    return router
}

// 配置文件示例 gateway.json
const defaultConfig = `{
  "models": [
    {"name": "gpt-3.5", "url": "https://api.openai.com/v1", 
     "api_key": "$OPENAI_API_KEY", "cost": 0.0015, "priority": 2},
    {"name": "gpt-4o", "url": "https://api.openai.com/v1",
     "api_key": "$OPENAI_API_KEY", "cost": 0.03, "priority": 1},
    {"name": "doubao", "url": "https://ark.cn-beijing.volces.com/api/v3",
     "api_key": "$DOUBAO_API_KEY", "cost": 0.0008, "priority": 0}
  ],
  "rate_limit": {"requests_per_second": 100, "burst": 200},
  "circuit_breaker": {"failure_threshold": 10, "cooldown_seconds": 30}
}`

运行

bash
echo "$defaultConfig" > gateway.json
go run main.go -config gateway.json

# 修改 gateway.json,增加一个新模型——网关自动生效
# 不需要重启 ❌ ./agent ✅

Day 5 完成。改配置不用重启了。