用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(‘\n‘)
        if errRet == io.EOF{
            // 表示读到文件的末尾
            break
        }
        if errRet != nil{
            // 表示读文件出问题
            err = errRet
            return
        }
        lineNo++
        line = strings.TrimSpace(line) // 取出空格
        if len(line) == 0 || line[0] == ‘\n‘ || line[0] == ‘+‘ || line[0] == ‘;‘{
            // 当前行为空行或者是注释行等
            continue
        }
        arr := strings.Split(line,"=") // 通过=进行切割取出k/v结构
        if len(arr) == 0{
            fmt.Printf("invalid config,line:%d\n",lineNo)
            continue
        }
        key := strings.TrimSpace(arr[0])
        if len(key) == 0{
            fmt.Printf("invalid config,line:%d\n",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\n",c.filename,err)
                return
            }
            defer file.Close()
            fileInfo,err := file.Stat()
            if err != nil{
                fmt.Printf("stat %s failed,err:%v\n",c.filename,err)
                return
            }
            curModifyTime := fileInfo.ModTime().Unix()
            fmt.Printf("%v --- %v\n",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

原文地址:https://www.cnblogs.com/zhaof/p/8593204.html

时间: 2024-07-29 23:12:12

用Go自己实现配置文件热加载功能的相关文章

cocos2d-x+lua代码热加载(Hot Swap)的研究

代码热加载跟自动更新无关,主要目的是在程序运行的时候动态的替换代码,从而实现不重启程序而更新代码的目的.最理想的情况当然是我修改完代码并保存,然后就可以直接在游戏中看到修改后的效果,这个在实际开发过程中会大大提高效率. 即便达不到理想情况,我们也希望可以实现部分热加载,从而简化操作.例如我们可以仅仅对配置文件.消息文件.界面文件实现热加载,这样策划更新数据后可以直接在游戏中看结果,而不需要重新打开客户端去跑任务. 热加载主要原理其实很简单,lua require文件都会缓存在package.lo

gitbook 入门教程之解决windows热加载失败问题

破镜如何贴花黄 gitbook 在 Windows 系统无法热加载,总是报错! gitbook 是一款文档编写利器,可以方便地 markdown 输出成美观优雅的 html ,gitbook serve 启动服务器后,原来相貌平平的 markdown 丑小鸭摇身一变就成了倾国倾城的 html 绝色佳人. 如果源文件发生更改,Windows 却无法按照预期那样重启服务器,直接抛出一个异常,立即终止了 markdown 的化妆. Restart after change in file README

java热加载

应用服务器一般都支持热部署(Hot Deployment),更新代码时把新编译的确类 替换旧的就行,后面的程序就执行新类中的代码.这也是由各种应用服务器的独 有的类加载器层次实现的.那如何在我们的程序中也实现这种热加载功能呢?即 要在虚拟机不关闭的情况下(比如一个),换个类,JVM 就知道加载这个新类,执 行新类中的逻辑呢?下面就简单演示这样一个热加载的例子,首先大致了解一下 类加载器. 标准 Java 启动器的类加载器层次 1. 引导类加载器(bootstrap): 加载内核 API,如 rt

Java热加载的实现

应用服务器一般都支持热部署或者热加载(Hot Deployment或者Hot Swap),即更新代码保存时把新编译类替换旧的类,后面的程序就执行新类中的代码.这也是由各种应用服务器的独有的类加载器层次实现的. 那如何在我们的程序中也实现这种热加载功能呢? 即要在不重启JVM虚拟机的情况下,换个类,JVM就知道加载这个新类,执行新类中的逻辑. 首先大致了解一下类加载器: 标准 Java 启动器的类加载器层次 1. 引导类加载器(bootstrap):加载内核 API,如 rt.jar(java.l

SpringBoot+gradle+idea实现热部署和热加载

前言 因为之前使用myeclipes的同学就知道,在使用myeclipes的时候,java文件或者jsp文件写完之后会被直接热加载到部署的容器中,从而在开发的时候,不同经常去重启项目,从而达到了增加开发效率的目的. 但是现在切换到SpringBoot之后,因为没有外部容器的支持,而且使用gradle去构建项目,再加上idea默认不会自动编译的特性,最终导致开发项目的时候需要经常重启项目,这是我们不愿意看到的. 为了提高开发效率,我们下面将优化我们的SpringBoot模版.方案经过验证,可放心使

原来热加载如此简单,手动写一个 Java 热加载吧

1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境. 2. 热加载与热部署的区别 首先,不管是热加载还是热部署,都可以在不重启服务的情况下编译/部署项目,都是基于 Java 的类加载器实现的. 那么两者到底有什么区别呢? 在部署方式上: 热部署是在服务器运行时重新部署项目. 热加载是在运行时重新加载 class. 在实现原理上: 热部署是直接重新

Java 热加载

1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境. 2. 热加载与热部署的区别 首先,不管是热加载还是热部署,都可以在不重启服务的情况下编译/部署项目,都是基于 Java 的类加载器实现的. 那么两者到底有什么区别呢? 在部署方式上: 热部署是在服务器运行时重新部署项目. 热加载是在运行时重新加载 class. 在实现原理上: 热部署是直接重新

mybatis热加载的实现

最近在使用mybatis,由于是刚刚开始用,用的并不顺手,目前是感觉有2个地方非常的不好用: 1.mybatis调试不方便 由于dao层只有接口,实现只是一个map的xml文件,想加断点都没有地方加,直接导致的后果就是有时候出错了,完全是各种闭眼尝试,抓狂中...倒是可以把调试级别改成debug,会把执行的sql,以及参数都输出到控制台,可是一改成debug,那控制台输出的内容,就实在多到让人发指,甚至都会影响到代码的编写及调试,而且输出日志跟打断点调试根本就不是一个级别的.目前仍旧无法解决改问

java的热部署和热加载

ps:热部署和热加载其实是两个类似但不同的概念,之前理解不深,so,这篇文章重构了下. 一.热部署与热加载 在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载. 对于Java应用程序来说,热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载class,从而升级应用. 二.实现原理 热加载的实现原理主要依赖java的类加载机制,在实现方式可以概括为在容器启动的时候起一条后台线程,定时的检测类文件的时间戳变化,如果类的时间戳变掉了,则将类重新载入. 对比反射机制,反射是在运