golang中使用原子操作监听配置更新

配置及代码文件

{
    "name":"sasuke",
    "age":25,
    "gender":"male",
    "score":99.5
}
develop.json
package main

import (
    "crypto/md5"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "os"
    "sync"
    "sync/atomic"
    "time"
)

type Student struct {
    Name   string  `json:"name"`
    Age    int     `json:"age"`
    Gender string  `json:"gender"`
    Score  float32 `json:"score"`
}

// 声明一个全局的变量,存放上一次文件变更时文件的hash值
var lastFileHash string = ""

// 生命一个全局的结构体对象配置,项目中使用这个全局变量,在业务中使用
var StudentConfig *Student

// 获取配置的值
func loadConfig(filePath string) (*Student, error) {
    file, errFile := os.Open(filePath)
    if errFile != nil {
        return nil, errFile
    }

    defer file.Close()

    //NewDecoder创建一个从file读取并解码json对象的*Decoder,解码器有自己的缓冲,并可能超前读取部分json数据。
    decoder := json.NewDecoder(file)

    conf := Student{}
    //Decode从输入流读取下一个json编码值并保存在v指向的值里
    errDecoder := decoder.Decode(&conf)
    if errDecoder != nil {
        return nil, errDecoder
    }

    return &conf, nil
}

// 获取文件的hash值
func getFileHash(filePath string) (string, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", err
    }
    defer file.Close()

    m := md5.New()
    _, err = io.Copy(m, file)
    if err != nil {
        return "", err
    }

    return hex.EncodeToString(m.Sum(nil)), nil
}

// 每隔5秒校验一下配置文件有没有更新,更新的话就给监听的协裎发通知
func checkFileChanged(configValue *atomic.Value, cond *sync.Cond, filePath string) {

    for {
        time.Sleep(time.Second * 5)
        // 如果文件变更了,那么它的hash值会改变
        fileHash, errFileHash := getFileHash(filePath)
        if errFileHash != nil {
            // 打log、上报告警等...
            fmt.Println("errFileHash: ", errFileHash)
            continue
        }
        // 文件没有变更,不用管
        if fileHash == lastFileHash {
            continue
        }

        // 文件有变更
        // 因为只有一个协裎在用这个变量,所以不存在竞争问题,直接赋值就好
        lastFileHash = fileHash
        // 加载最新的配置
        newConf, errNewConf := loadConfig(filePath)
        if errNewConf != nil {
            // 打log、上报告警等...
            fmt.Println("errNewConf: ", errNewConf)
            continue
        }
        // fmt.Println("newConf: ", newConf)
        // atomoc.Value Store
        configValue.Store(newConf)
        // 通知:配置已变更
        cond.Broadcast()
    }
}

// 获取更新的配置,实际上可能有多个协裎在监听~
func loadConfigValue(configValue *atomic.Value, cond *sync.Cond) {
    for {
        cond.L.Lock()
        // 等待配置变更的信号
        cond.Wait()
        // 读取新的配置信息,并且复制给全局变量
        // s1 := configValue.Load()
        // fmt.Printf("s1: %T, %v \n", s1, s1)
        // Notice 注意这里要转化为 *Student
        StudentConfig = configValue.Load().(*Student)
        cond.L.Unlock()
    }
}

func main() {
    var configValue atomic.Value
    var cond = sync.NewCond(&sync.Mutex{})

    // Notice 注意在协裎中要使用同一个 atomic.Value 变量,所以需要传指针!

    // check & store & broadcast
    go checkFileChanged(&configValue, cond, "./develop.json")

    // listening & load
    go loadConfigValue(&configValue, cond)

    // 在主协裎打印一下最新的配置看看
    go func() {
        for {
            time.Sleep(time.Second * 2)
            fmt.Println("最新的配置:", StudentConfig)
        }
    }()

    select {}

}
main.go

参考文章

uber风格指南中使用go.uber.org/atomic

如何读取yaml,json,ini等配置文件

golang 计算字符以及文件的 hash 值

posted on 2022-12-29 16:45  江湖乄夜雨  阅读(137)  评论(0编辑  收藏  举报