Support hot update config

This commit is contained in:
DullJZ
2025-09-24 21:15:11 +08:00
parent 4b5c9a658d
commit c92b874bba
6 changed files with 435 additions and 6 deletions

View File

@@ -29,11 +29,15 @@ func main() {
flag.StringVar(&configFile, "config", "config/config.yaml", "Path to configuration file")
flag.Parse()
// 加载配置
cfg, err := config.Load(configFile)
// 创建配置管理器
configManager, err := config.NewManager(configFile)
if err != nil {
log.Fatalf("Failed to load configuration: %v", err)
log.Fatalf("Failed to create config manager: %v", err)
}
defer configManager.Close()
// 获取初始配置
cfg := configManager.GetConfig()
// 初始化数据库
if err := database.Initialize(&cfg.Database); err != nil {
@@ -88,6 +92,23 @@ func main() {
cfg.S3API.ProxyMode,
)
// 注册配置热更新回调
configManager.OnConfigChange(func(newConfig *config.Config) {
log.Println("Configuration changed, updating components...")
// 更新bucket manager配置
if err := bucketManager.UpdateConfig(newConfig); err != nil {
log.Printf("Failed to update bucket manager config: %v", err)
}
// 更新负载均衡器配置
if err := lb.UpdateStrategy(newConfig.Balancer.Strategy); err != nil {
log.Printf("Failed to update load balancer strategy: %v", err)
}
log.Println("Components updated successfully")
})
// 设置路由
router := mux.NewRouter()

11
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.31.1
github.com/aws/aws-sdk-go-v2/credentials v1.18.5
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.0
github.com/fsnotify/fsnotify v1.9.0
github.com/gorilla/mux v1.8.1
github.com/prometheus/client_golang v1.23.2
gopkg.in/yaml.v3 v3.0.1
@@ -14,6 +15,7 @@ require (
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1
modernc.org/sqlite v1.39.0
)
require (
@@ -34,7 +36,9 @@ require (
github.com/aws/smithy-go v1.22.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
@@ -42,16 +46,23 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

47
go.sum
View File

@@ -44,10 +44,18 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -70,10 +78,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
@@ -84,6 +96,8 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -97,12 +111,19 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -119,3 +140,29 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View File

@@ -87,7 +87,7 @@ func (b *Balancer) GetStrategy() string {
// 允许在运行时更改负载均衡策略
func (b *Balancer) SetStrategy(strategyName string) error {
var strategy Strategy
switch strategyName {
case "round-robin":
strategy = NewRoundRobinStrategy()
@@ -100,11 +100,16 @@ func (b *Balancer) SetStrategy(strategyName string) error {
default:
return fmt.Errorf("unknown balancer strategy: %s", strategyName)
}
b.strategy = strategy
return nil
}
// UpdateStrategy 更新负载均衡策略(热更新用)
func (b *Balancer) UpdateStrategy(strategyName string) error {
return b.SetStrategy(strategyName)
}
// SetMetrics 设置指标服务
func (b *Balancer) SetMetrics(metrics *metrics.Metrics) {
b.metrics = metrics

View File

@@ -3,6 +3,7 @@ package bucket
import (
"context"
"fmt"
"log"
"sync"
"time"
@@ -280,7 +281,7 @@ func (m *Manager) GetVirtualBuckets() []*BucketInfo {
func (m *Manager) GetRealBuckets() []*BucketInfo {
m.mu.RLock()
defer m.mu.RUnlock()
var real []*BucketInfo
for _, b := range m.buckets {
if !b.IsVirtual() {
@@ -289,3 +290,101 @@ func (m *Manager) GetRealBuckets() []*BucketInfo {
}
return real
}
// UpdateConfig 更新配置(支持热更新)
func (m *Manager) UpdateConfig(newConfig *config.Config) error {
m.mu.Lock()
defer m.mu.Unlock()
log.Println("Updating bucket manager configuration...")
// 更新配置引用
oldConfig := m.config
m.config = newConfig
// 检查是否需要重新创建存储桶
needsRestart := m.checkIfRestartNeeded(oldConfig, newConfig)
if needsRestart {
log.Println("Bucket configuration changed significantly, recreating buckets...")
// 停止现有的监控
if m.healthMonitor != nil {
m.healthMonitor.Stop()
}
if m.statsMonitor != nil {
m.statsMonitor.Stop()
}
// 重新创建bucket映射
m.buckets = make(map[string]*BucketInfo)
// 初始化所有存储桶客户端
for _, bucketCfg := range newConfig.Buckets {
if !bucketCfg.Enabled {
continue
}
client, err := createS3Client(bucketCfg)
if err != nil {
// 如果创建失败,恢复旧配置
m.config = oldConfig
return fmt.Errorf("failed to create S3 client for bucket %s: %v", bucketCfg.Name, err)
}
info := &BucketInfo{
Config: bucketCfg,
Client: client,
Available: true,
LastChecked: time.Now(),
}
m.buckets[bucketCfg.Name] = info
}
// 重新初始化监控
m.initHealthMonitoring()
} else {
// 只更新监控间隔(需要重启监控来改变间隔)
log.Println("Updating monitoring intervals...")
if m.healthMonitor != nil {
m.healthMonitor.Stop()
}
if m.statsMonitor != nil {
m.statsMonitor.Stop()
}
// 重新初始化监控以应用新的间隔
m.initHealthMonitoring()
}
log.Println("Bucket manager configuration updated successfully")
return nil
}
// checkIfRestartNeeded 检查是否需要重启bucket manager
func (m *Manager) checkIfRestartNeeded(oldConfig, newConfig *config.Config) bool {
// 检查bucket数量变化
if len(oldConfig.Buckets) != len(newConfig.Buckets) {
return true
}
// 检查bucket配置变化
for i, oldBucket := range oldConfig.Buckets {
if i >= len(newConfig.Buckets) {
return true
}
newBucket := newConfig.Buckets[i]
// 检查关键配置项
if oldBucket.Name != newBucket.Name ||
oldBucket.Endpoint != newBucket.Endpoint ||
oldBucket.AccessKeyID != newBucket.AccessKeyID ||
oldBucket.SecretAccessKey != newBucket.SecretAccessKey ||
oldBucket.Region != newBucket.Region ||
oldBucket.Enabled != newBucket.Enabled ||
oldBucket.Virtual != newBucket.Virtual {
return true
}
}
return false
}

246
internal/config/manager.go Normal file
View File

@@ -0,0 +1,246 @@
package config
import (
"log"
"os"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
// Manager 配置管理器,支持热更新
type Manager struct {
configFile string
config *Config
mutex sync.RWMutex
watcher *fsnotify.Watcher
callbacks []func(*Config)
stopChan chan struct{}
lastModTime time.Time
pollingTicker *time.Ticker
}
// NewManager 创建新的配置管理器
func NewManager(configFile string) (*Manager, error) {
// 初始加载配置
cfg, err := Load(configFile)
if err != nil {
return nil, err
}
// 获取文件的初始修改时间
fileInfo, err := os.Stat(configFile)
if err != nil {
return nil, err
}
manager := &Manager{
configFile: configFile,
config: cfg,
callbacks: make([]func(*Config), 0),
stopChan: make(chan struct{}),
lastModTime: fileInfo.ModTime(),
}
// 同时启用fsnotify和轮询监听
// 这样可以确保在Docker挂载等场景下也能正常工作
manager.initWatching()
return manager, nil
}
// initWatching 初始化文件监听同时使用fsnotify和轮询
func (m *Manager) initWatching() {
// 尝试启用fsnotify
watcher, err := fsnotify.NewWatcher()
if err == nil {
if err := watcher.Add(m.configFile); err == nil {
m.watcher = watcher
log.Println("fsnotify watcher enabled for config file")
go m.watchConfig()
} else {
log.Printf("Failed to add file to fsnotify watcher: %v", err)
watcher.Close()
}
} else {
log.Printf("Failed to create fsnotify watcher: %v", err)
}
// 同时启用轮询模式(作为备用和补充)
// 在Docker挂载等场景下轮询更可靠
m.pollingTicker = time.NewTicker(3 * time.Second)
log.Println("Config file polling enabled (3s interval)")
go m.pollConfig()
}
// pollConfig 轮询检查配置文件变化
func (m *Manager) pollConfig() {
for {
select {
case <-m.pollingTicker.C:
fileInfo, err := os.Stat(m.configFile)
if err != nil {
log.Printf("Failed to stat config file during polling: %v", err)
continue
}
// 检查文件修改时间
if fileInfo.ModTime().After(m.lastModTime) {
log.Printf("Config file %s modified (detected by polling), reloading...", m.configFile)
m.lastModTime = fileInfo.ModTime()
m.reloadConfig()
}
case <-m.stopChan:
return
}
}
}
// GetConfig 获取当前配置(线程安全)
func (m *Manager) GetConfig() *Config {
m.mutex.RLock()
defer m.mutex.RUnlock()
// 返回配置的副本以避免并发修改
configCopy := *m.config
return &configCopy
}
// OnConfigChange 注册配置变化回调
func (m *Manager) OnConfigChange(callback func(*Config)) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.callbacks = append(m.callbacks, callback)
}
// watchConfig 监听配置文件变化fsnotify模式
func (m *Manager) watchConfig() {
for {
select {
case event, ok := <-m.watcher.Events:
if !ok {
return
}
// 只处理修改和重命名事件
if event.Op&fsnotify.Write == fsnotify.Write ||
event.Op&fsnotify.Rename == fsnotify.Rename {
log.Printf("Config file %s modified (detected by fsnotify), reloading...", m.configFile)
// 更新最后修改时间以避免轮询重复触发
if fileInfo, err := os.Stat(m.configFile); err == nil {
m.lastModTime = fileInfo.ModTime()
}
m.reloadConfig()
}
case err, ok := <-m.watcher.Errors:
if !ok {
return
}
log.Printf("Config watcher error: %v", err)
case <-m.stopChan:
return
}
}
}
// reloadConfig 重新加载配置
func (m *Manager) reloadConfig() {
// 添加延迟以防止编辑器的多次写入事件
time.Sleep(100 * time.Millisecond)
// 加载新配置
newConfig, err := Load(m.configFile)
if err != nil {
log.Printf("Failed to reload config: %v", err)
return
}
// 更新配置
m.mutex.Lock()
oldConfig := m.config
m.config = newConfig
callbacks := make([]func(*Config), len(m.callbacks))
copy(callbacks, m.callbacks)
m.mutex.Unlock()
log.Printf("Configuration reloaded successfully")
// 异步调用回调函数
go func() {
for _, callback := range callbacks {
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Config change callback panic: %v", r)
}
}()
callback(newConfig)
}()
}
}()
// 记录重要配置变更
m.logConfigChanges(oldConfig, newConfig)
}
// logConfigChanges 记录配置变更
func (m *Manager) logConfigChanges(oldConfig, newConfig *Config) {
// 检查服务器端口变化
if oldConfig.Server.Port != newConfig.Server.Port {
log.Printf("Server port changed: %d -> %d (restart required)",
oldConfig.Server.Port, newConfig.Server.Port)
}
// 检查数据库配置变化
if oldConfig.Database.DSN != newConfig.Database.DSN {
log.Printf("Database DSN changed (restart required)")
}
// 检查存储桶数量变化
if len(oldConfig.Buckets) != len(newConfig.Buckets) {
log.Printf("Bucket count changed: %d -> %d",
len(oldConfig.Buckets), len(newConfig.Buckets))
}
// 检查负载均衡策略变化
if oldConfig.Balancer.Strategy != newConfig.Balancer.Strategy {
log.Printf("Load balancer strategy changed: %s -> %s",
oldConfig.Balancer.Strategy, newConfig.Balancer.Strategy)
}
// 检查代理模式变化
if oldConfig.S3API.ProxyMode != newConfig.S3API.ProxyMode {
log.Printf("S3 API proxy mode changed: %t -> %t",
oldConfig.S3API.ProxyMode, newConfig.S3API.ProxyMode)
}
// 检查指标配置变化
if oldConfig.Metrics.Enabled != newConfig.Metrics.Enabled {
log.Printf("Metrics enabled changed: %t -> %t",
oldConfig.Metrics.Enabled, newConfig.Metrics.Enabled)
}
}
// Close 关闭配置管理器
func (m *Manager) Close() error {
// 停止监听协程
close(m.stopChan)
// 停止轮询
if m.pollingTicker != nil {
m.pollingTicker.Stop()
}
// 关闭fsnotify watcher
if m.watcher != nil {
return m.watcher.Close()
}
return nil
}