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 {}
}