最近在捣鼓golang,其中用线上数据全放在redis里,以前写死的配置有点不太适合了,热加载才是王道!

俗话说不会搜索的程序员不是一个好程序员(误

观摩了一下其他大佬的写法,大多都是采用定时器+goroutine实现的。难度不大,开始码

配置文件结构体

假设一下我们的配置文件是json文件,那应该就是下面这样

// conf.json
{
  "host": "127.0.0.1",
  "port": 6379,
  "passwd": "",
  "db": 0
}

首先肯定是要写一个跟它一模一样的结构体出来

//json配置文件结构体
type Content struct {
    Host   string `json:"host"`
    Port   int    `json:"port"`
    Passwd string `json:"passwd"`
    Db     int    `json:"db"`
}

如果继续在此结构体上编写操作函数不太稳妥,序列化json也是直接操作的指针。之前拆散它们的试错,最后还是让它们在一起了233

优化结构体

将文件名,同步锁,最后修改时间,配置文件结构体整合一起

//配置结构体
type Config struct {
    Filename       string
    LastModifyTime int64
    Lock           *sync.RWMutex
    Data           interface{}
}

编写实例配置的工厂函数(这个有点约定俗成的规矩,具体出处你们自己去考证吧,好处挺多的)

func NewConfig(filename string, data interface{}) *Config {
    conf := &Config{
        Filename: filename,
        Data: data,
        Lock: &sync.RWMutex{},
    }

    conf.parse()

    go conf.reload()

    return conf
}

这里把配置结构体配置了默认参数,外部传入参数是配置文件的文件名配置文件结构体,这里data的类型为接口类型,好处是Config可以独立出来,代码多处复用。我们还使用了conf.parse()方法,第一次解析文件,go conf.reload()方法单独起一个goroutine跑(具体效果看后面),结果当然返回本体了

解析函数

暴力读取文件内容,放进json序列化。读文件出错没得商量退群吧你,解json出错返回失败,成功时锁住c.Data写入

func (c *Config) parse() bool {
    //记录最后修改时间
    fileInfo, _ := os.Stat(c.Filename)
    c.LastModifyTime = fileInfo.ModTime().Unix()

    //读取文件内容
    file, err := ioutil.ReadFile(c.Filename)
    if err != nil {
        log.Println("读取配置文件失败:", err)
        //直接退出程序
        os.Exit(1)
    }

    //解json
    c.Lock.Lock()
    if err = json.Unmarshal(file, &c.Data); err != nil {
        log.Println("解析json出错:", err)
        return false
    }

    log.Printf("文件内容 = %+v\n", c.Data)

    return true
}

重载函数

这里新建了个5秒钟的定时器,隔5秒检查最后更改时间,与上次不同就再次解析,解析完成锁的释放

func (c *Config) reload() {
     ticker := time.NewTicker(time.Second * 5)
     for _ = range ticker.C {
          func() {
               fileInfo, _ := os.Stat(c.Filename)
               currModifyTime := fileInfo.ModTime().Unix()
               if currModifyTime > c.LastModifyTime {
                    if c.parse() {
                         log.Println("重新加载配置文件conf.json")
                    }
                    c.Lock.Unlock()
               }
          }()
     }
}

运行

用着我们最开始使用的配置文件结构体NewConfig()一下,这里要用select {}阻塞一下程序,chan同理,实时的更改conf.json可以直接读到程序里更改了

func main() {
    log.Println("开始运行...")

    data := &Content{
        Host:   "127.0.0.1",
        Port:   6379,
        Passwd: "",
        Db:     0,
    }
    _ = NewConfig("conf.json", data)
    select {}

    //end := make(chan bool, 1)
    //<-end
}

代码

总结知识点

  1. io操作
  2. 同步
  3. 定时器
  4. 时间
  5. 通道

标签: 热加载

已有 3 条评论

  1. 天呐,你咋还研究golang去了,我PY还没开始学呢

    1. 大佬可以py你吗( ꒪⌓꒪)

      1. 可啪,居然有大佬想py我

添加新评论