zoukankan      html  css  js  c++  java
  • 用Go自己实现配置文件热加载功能

    说到配置文件热加载,这个功能在很多框架中都提供了,如beego,实现的效果就是当你修改文件后,会把你修改后的配置重新加载到配置文件中,而不用重启程序,这个功能在日常中还是非常实用的,毕竟很多时候,线上的配置文件不是想改就能改的。

    这次就自己实现一个配置文件的热加载功能的包,并通过一个简单的例子对完成的包进行使用验证

    配置文件热加载包的是实现

    其实整体的思路还是比较简单的,当获取配置文件内容后,会开启一个goroutine,去 循环读配置文件,当然这里不可能不限制的一直循环,而是设置了一个定时器,定时去读文件,根据文件的修改时间是否变化,从而确定是否重新reload配置文件

    实现的config 包的文件结构为:

    ├── config.go
    └── config_notify.go

    config.go:代码的主要处理逻辑
    config_notify.go:主要定义了一个接口,用于当文件修改时间变化的时候执行回调

    config_notify.go的代码相对来说比较简单,我们先看看这个代码:

    package config
    
    // 定义一个通知的接口
    type Notifyer interface {
        Callback(*Config)
    }

    这样当我们实现了Callback这个方法的时候,我们就实现了Notifyer这个接口,具体的调用在后面会说

    在config.go中我们顶一个了一个结构体:

    type Config struct {
        filename string
        lastModifyTime int64
        data map[string]string
        rwLock sync.RWMutex
        notifyList []Notifyer
    }

    结构体中主要包含几个字段:
    filename:配置文件名字
    lastModifyTime:配置文件的最后修改时间
    data:用于将从配置文件中读取的内容存储为map
    rwlock:读写锁
    notifyList:用于将调用该包的程序追加到切片中,用于通知调用上面在config_notify.go定义的callback回调函数

    关于读取配置文件中的内容并存储到map中,这里定义了一个方法实现:

    func (c *Config) parse()(m map[string]string,err error){
        // 读文件并或将文件中的数据以k/v的形式存储到map中
        m = make(map[string]string,1024)
        file,err := os.Open(c.filename)
        if err != nil{
            return
        }
        var lineNo int
        reader := bufio.NewReader(file)
        for{
            // 一行行的读文件
            line,errRet := reader.ReadString('
    ')
            if errRet == io.EOF{
                // 表示读到文件的末尾
                break
            }
            if errRet != nil{
                // 表示读文件出问题
                err = errRet
                return
            }
            lineNo++
            line = strings.TrimSpace(line) // 取出空格
            if len(line) == 0 || line[0] == '
    ' || line[0] == '+' || line[0] == ';'{
                // 当前行为空行或者是注释行等
                continue
            }
            arr := strings.Split(line,"=") // 通过=进行切割取出k/v结构
            if len(arr) == 0{
                fmt.Printf("invalid config,line:%d
    ",lineNo)
                continue
            }
            key := strings.TrimSpace(arr[0])
            if len(key) == 0{
                fmt.Printf("invalid config,line:%d
    ",lineNo)
                continue
            }
            if len(arr) == 1{
                m[key] = ""
                continue
            }
            value := strings.TrimSpace(arr[1])
            m[key] = value
        }
        return
    }

    而最后我们就需要一个定时器,每隔一段时间判断配置文件的最后修改时间是否变化,如果变化则重新读取一次文件并将文件内容存储到map中。

    func (c *Config) reload(){
        // 这里启动一个定时器,每5秒重新加载一次配置文件
        ticker := time.NewTicker(time.Second*5)
        for _ = range ticker.C{
            func(){
                file,err := os.Open(c.filename)
                if err != nil{
                    fmt.Printf("open %s failed,err:%v
    ",c.filename,err)
                    return
                }
                defer file.Close()
                fileInfo,err := file.Stat()
                if err != nil{
                    fmt.Printf("stat %s failed,err:%v
    ",c.filename,err)
                    return
                }
                curModifyTime := fileInfo.ModTime().Unix()
                fmt.Printf("%v --- %v
    ",curModifyTime,c.lastModifyTime)
                //判断文件的修改时间是否大于最后一次修改时间
                if curModifyTime > c.lastModifyTime{
                    m,err := c.parse()
                    if err != nil{
                        fmt.Println("parse failed,err:",err)
                        return
                    }
                    c.rwLock.Lock()
                    c.data = m
                    c.rwLock.Unlock()
                    for _, n:=range c.notifyList{
                        n.Callback(c)
                    }
                    c.lastModifyTime = curModifyTime
                }
            }()
        }
    }

    关于config完整的代码地址:https://github.com/pythonsite/go_simple_code/tree/master/config

    一个演示上述包的例子

    这里一个简单的例子,代码的逻辑也非常简单就是写一个循环从配置文件读取配置信息,当然这里是为了测试效果,写成了循环。这里有个问题需要注意,就是在配置文件中存放数据的时候应该是如下格式存储

    listen_addr = localhost
    server_port = 1000
    
    # Nginx addr
    nginx_addr = 192.168.1.2:9090

    测试代码的主要结构如下:

    ├── config.conf
    └── main.go

    config.conf为配置文件
    main.go 为主要测试代码

    type AppConfig struct {
        port int
        nginxAddr string
    }
    
    type AppconfigMgr struct {
        config atomic.Value
    }
    
    var appConfigMgr = &AppconfigMgr{}
    
    
    func(a *AppconfigMgr)Callback(conf *config.Config){
    
        var appConfig = &AppConfig{}
    
        port,err := conf.GetInt("server_port")
        if err != nil{
            fmt.Println("get port failed,err:",err)
            return
        }
        appConfig.port = port
        fmt.Println("port:",appConfig.port)
        nginxAddr,err := conf.GetString("nginx_addr")
        if err != nil{
            fmt.Println("get nginx addr failed,err:",err)
            return
        }
        appConfig.nginxAddr = nginxAddr
        fmt.Println("nginx addr :",appConfig.nginxAddr)
    
        appConfigMgr.config.Store(appConfig)
    
    }
    
    func run(){
        for {
            // 每5秒打印一次数据,查看自己更改配置文件后是否可以热刷新
            appConfig := appConfigMgr.config.Load().(*AppConfig)
            fmt.Println("port:",appConfig.port)
            fmt.Println("nginx addr:",appConfig.nginxAddr)
            time.Sleep(5* time.Second)
        }
    }
    
    func main() {
        conf,err := config.NewConfig("/Users/zhaofan/go_project/src/go_dev/13/config_test/config.conf")
        if err != nil{
            fmt.Println("parse config failed,err:",err)
            return
        }
        //打开文件获取内容后,将自己加入到被通知的切片中
        conf.AddNotifyer(appConfigMgr)
    
        var appConfig = &AppConfig{}
    
        appConfig.port,err = conf.GetInt("server_port")
        if err != nil{
            fmt.Println("get port failed,err:",err)
            return
        }
        fmt.Println("port:",appConfig.port)
    
        appConfig.nginxAddr,err = conf.GetString("nginx_addr")
        if err != nil{
            fmt.Println("get nginx addr failed,err:",err)
            return
        }
        fmt.Println("nginx addr:",appConfig.nginxAddr)
        appConfigMgr.config.Store(appConfig)
        run()
    
    }

    上面代码中有一段代码非常重要:

    func(a *AppconfigMgr)Callback(conf *config.Config){
    
        var appConfig = &AppConfig{}
    
        port,err := conf.GetInt("server_port")
        if err != nil{
            fmt.Println("get port failed,err:",err)
            return
        }
        appConfig.port = port
        fmt.Println("port:",appConfig.port)
        nginxAddr,err := conf.GetString("nginx_addr")
        if err != nil{
            fmt.Println("get nginx addr failed,err:",err)
            return
        }
        appConfig.nginxAddr = nginxAddr
        fmt.Println("nginx addr :",appConfig.nginxAddr)
    
        appConfigMgr.config.Store(appConfig)
    
    }

    这里我们实现了Callback方法,同时就实现了我们在config包中定义的那个接口

    测试效果如下,当我们更改配置文件后,程序中的配置文件也被重新加载

    完整的测试代码地址:https://github.com/pythonsite/go_simple_code/tree/master/config_test

  • 相关阅读:
    使用IntelliJ IDEA和Maven管理搭建+Web+Tomcat开发环境
    MVC中使用SignalR打造酷炫实用的即时通讯功能(轉載)
    动软软件 生成 实体类模板(EnterpriseFrameWork框架)
    MVC+Ninject+三层架构+代码生成 总结(一、數據庫)
    MVC+Ninject+三层架构+代码生成 总结(三、實體類)
    1.0EnterpriseFrameWork 框架学习
    Excel操作类
    MVC+Ninject+三层架构+代码生成 总结(二、建項目)
    软件添加注册功能
    MVC+Ninject+三层架构+代码生成 总结(五、Ninject)
  • 原文地址:https://www.cnblogs.com/zhaof/p/8593204.html
Copyright © 2011-2022 走看看