Как в Golang сделать горячую перезагрузку данных из конфига

Возможность перезагрузить (reload) конфигурацию без перезапуска (restart) программы стало не излишеством, а производственной необходимостью. Например, такая функция есть в Nginx и Postgresql (не для всех настроек, но для большинства).

Основная проблема при решении подобных задач это обеспечить одновременный доступ для чтения и записи к общему объекту содержащему конфигурацию. Существует много способов сделать это. В Go идеально подойдёт подход с использованием мьютекса чтения/записи.

Во-первых, базовая структура конфигурации, глобальная (для пакета) переменная и аналогичная блокировка:

import (
  "os"
  "log"
  "sync"
  "syscall"
  "os/signal"
  "io/ioutil"
  "encoding/json"
)
type Config struct {
  Mode string
  CacheSize int
}
var (
  config *Config
  configLock = new(sync.RWMutex)
)

Затем нужна функция, которая загружает конфигурацию:

func loadConfig(fail bool) {
  file, err := ioutil.ReadFile("config.json")
  if err != nil {
    log.Println("open config: ", err)
    if fail { os.Exit(1) }
  }
  temp := new(Config)
  if err = json.Unmarshal(file, temp); err != nil {
    log.Println("parse config: ", err)
    if fail { os.Exit(1) }
  }
  configLock.Lock()
  config = temp
  configLock.Unlock()
}

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

Далее нужен способ доступа к текущей конфигурации:

func GetConfig() *Config {
  configLock.RLock()
  defer configLock.RUnlock()
  return config
}

Теперь осталось сделать начальную нагрузку, а также вызвать перезагрузку. Для этого слушаем сигнал:

// go calls init on start
func init() {
  loadConfig(true)
  s := make(chan os.Signal, 1)
  signal.Notify(s, syscall.SIGUSR2)
  go func() {
    for {
      <-s
      loadConfig(false)
      log.Println("Reloaded")
    }
  }()
}