Используйте Go для самостоятельной реализации функции горячей загрузки конфигурационного файла

Go

Говоря о горячей загрузке файла конфигурации, эта функция предусмотрена во многих фреймворках, таких как beego.Эффект заключается в том, что при изменении файла измененная конфигурация будет перезагружена в файл конфигурации без перезапуска программы.Эта функция все еще очень практично в повседневной жизни.В конце концов, во многих случаях онлайн-файл конфигурации не может быть изменен, если вы хотите.

На этот раз я сам реализую пакет с функцией горячей загрузки конфигурационного файла и на простом примере проверю использование готового пакета.

Реализован пакет горячей загрузки конфигурационного файла

На самом деле общая идея относительно проста.Когда содержимое конфигурационного файла будет получено, запустится горутина для чтения конфигурационного файла в цикле.Конечно, без ограничений держать цикл невозможно, но таймер есть. настроен на регулярное чтение файла. Определите, следует ли перезагружать файл конфигурации, в зависимости от того, изменяется ли время модификации файла.

Файловая структура реализованного пакета конфигурации:

├── 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
}

Структура в основном содержит несколько полей:
имя файла: имя файла конфигурации
lastModifyTime: время последней модификации файла конфигурации.
данные: используется для хранения содержимого, считанного из файла конфигурации, в виде карты
rwlock: блокировка чтения-записи
notifyList: используется для добавления программы, вызывающей пакет, в слайс, используется для уведомления функции обратного вызова, определенной выше в config_notify.go.

Что касается чтения содержимого файла конфигурации и его сохранения на карте, здесь определяется реализация метода:

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
}

В конце концов, нам нужен таймер, чтобы определить, менялось ли время последней модификации конфигурационного файла каждый раз, и если это так, прочитать файл еще раз и сохранить содержимое файла на карте.

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
            }
        }()
    }
}

О полном кодовом адресе конфига:GitHub.com/сайт Python/…

Пример, демонстрирующий вышеуказанный пакет

Вот простой пример, логика кода тоже очень проста, нужно написать цикл для чтения конфигурационной информации из конфигурационного файла, конечно, он написан как цикл для проверки эффекта. Здесь следует отметить одну проблему, то есть при сохранении данных в конфигурационном файле они должны храниться в следующем формате

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)

}

Здесь мы реализуем метод обратного вызова и в то же время реализуем интерфейс, который мы определили в пакете конфигурации.

Эффект теста следующий, когда мы меняем файл конфигурации, файл конфигурации в программе также перезагружается

Полный адрес тестового кода:GitHub.com/сайт Python/…

 

Всех усилий стоит ждать с нетерпением, и каждая мечта должна быть орошена!