目录
概述
web项目,经常需要热启动各种各样的配置信息,一旦这些服务发生变更,我们需要重新启动web server,以使配置生效,实现配置热加载。这里有几种方法实现这个需求。
go 定时器协程实现
项目结构
首先来看一下整个项目的目录结构:
- dyconfig // 项目地址
- config // 配置文件目录
- api.yaml // 采用yaml格式文件
- global // 代码文件夹
- config
- config_define
- init
- reload
- go.mod // go package管理依赖的包文件
- go.sum // go package管理打包产生的文件
- main.go // web server的入口,主函数
代码细节
接下来依次看一下各文件的主体内容:
conf/api.yaml文件定义了配置项,包含server的host及port信息。
service: server: env: dev host: 127.0.0.1 port: 9902
global/init.go
package global import ( "context" "path" ) type Config struct { Conf struct { FilePath string LastModifyTime int64 } ctx context.Context cancel context.CancelFunc } func NewConfig() (*Config, error) { conf := new(Config) conf.ctx, conf.cancel = context.WithCancel(context.Background()) conf.Conf.FilePath = path.Join("./config", "api.yaml") APIconfig = conf.loadRoute() go conf.reload() //开启协程监听routeNotify go func() { for { select { case lastModifyTime, ok := <-routeNotify: if !ok { return } conf.Conf.LastModifyTime = lastModifyTime route := routeAtomic.Load().(*APIConf) if route != nil { APIconfig = route } } } }() return conf, nil } func (c *Config) Stop() { c.cancel() }
定义Config 根据LastModifyTime 判断是否有发生变化,FilePath为文件路径
go conf.reload()
开启协程监听routeNotify,routeNotify内容是文件修改时间的timestamp
global/reload.go
package global import ( "fmt" "gopkg.in/yaml.v3" "io/ioutil" "os" "sync/atomic" "time" ) const ( CheckInterval = 5 * time.Second ) var ( routeAtomic atomic.Value //原子性,解决并发问题 routeNotify = make(chan int64) //channel 放入timestamp ) func (c *Config) reload() { ticker := time.NewTicker(CheckInterval) defer ticker.Stop() for { select { case <-c.ctx.Done(): close(routeNotify) return case <-ticker.C: if f, err := os.Stat(c.Route.FilePath); err != nil { fmt.Println(err) } else if f.ModTime().Unix() != c.Route.LastModifyTime { if c.Route.LastModifyTime == 0 { c.Route.LastModifyTime = f.ModTime().Unix() } else { routeAtomic.Store(c.loadConfig()) routeNotify <- f.ModTime().Unix() fmt.Println("配置文件发生变化") } } } } } func (c *Config) loadConfig() *APIConf { if fp, err := ioutil.ReadFile(c.Route.FilePath); err != nil { fmt.Println(err) return nil } else { route := new(APIConf) if err := yaml.Unmarshal(fp, &route); err != nil { return nil } return route } }
定时器监听文件的修改时间与LastModifyTime是否相同,如果不同,则
package global var ( APIconfig = new(APIConf) )
package global type ServiceConf struct { Server struct { Env string `yaml:"env"` Host string `yaml:"host"` Port string `yaml:"port"` } `yaml:"server"` } type APIConf struct { Service ServiceConf `yaml:"service"` }
mian
package main import ( "dyconfig/global" "fmt" "github.com/gin-gonic/gin" ) func main() { if conf, err := global.NewConfig(); err != nil { // 初始化配置 fmt.Println(err) } else { defer conf.Stop() } gin.SetMode(gin.DebugMode) r := gin.Default() r.GET("/ping", func(context *gin.Context) { fmt.Println("当前host是: ", global.APIconfig.Service.Server.Host) fmt.Println("当前port是: ", global.APIconfig.Service.Server.Port) context.JSON( 200, gin.H{ "host": global.APIconfig.Service.Server.Host, "port": &global.APIconfig.Service.Server.Port, }) }) port := global.APIconfig.Service.Server.Port fmt.Println("当前host是: ", global.APIconfig.Service.Server.Host) fmt.Println("当前port是: ", global.APIconfig.Service.Server.Port) port = ":" + port _ = r.Run(port) }
调用示例
1.第一次调用,port为9902
2. 修改config ,port为9903
您可能感兴趣的文章: