go implement profile hot update and source code analysis based on viper

Keywords: github

The go third-party library github.com/spf13/viper reads and injects configuration files into the structure for ease of use.

Where

viperInstance := viper.New()	// viper instance
viperInstance.WatchConfig()
viperInstance.OnConfigChange(func(e fsnotify.Event) {
	log.Print("Config file updated.")
	viperLoadConf(viperInstance)    // Method of loading configuration
})

Configurable hot updates take effect without restarting the project's new configuration (there are more than one way to achieve hot loading, such as when the file was last modified).

Why do you write that?Why does this work immediately?Take a look at how viper achieves hot updates based on these two questions.

The core of the above code is in two places: the WatchConfig() method and the OnConfigChange() method.The WatchConfig() method is used to turn on event monitoring, determine if the file can be read properly after the user manipulates it, and inject the content into the config field of the viper instance. First, look at the WatchConfig() method:

func (v *Viper) WatchConfig() {
	go func() {
            // Create a new monitoring handler, open a protocol to start waiting for events
            // Read from I/O completion port and inject events into Event object: Watcher.Events
		watcher, err := fsnotify.NewWatcher()    
		if err != nil {
			log.Fatal(err)
		}
		defer watcher.Close()

		// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
		filename, err := v.getConfigFile()    
		if err != nil {
			log.Println("error:", err)
			return
		}

		configFile := filepath.Clean(filename)        //Configuration file E:\etc\bizsvc\config.yml
		configDir, _ := filepath.Split(configFile)    // E:\etc\bizsvc\

		done := make(chan bool)
		go func() {
			for {
				select {
                // The event object read has two properties, Name is E:\etc\bizsvc\config.yml, Op is write (operation on file)
				case event := <-watcher.Events:
		// Clear the inside., and the elements before it, and clear the current path., to determine if the file for the operation is a configFile
					if filepath.Clean(event.Name) == configFile {
                // If the file is created or written
						if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
							err := v.ReadInConfig()
							if err != nil {
								log.Println("error:", err)
							}
							v.onConfigChange(event)
						}
					}
				case err := <-watcher.Errors:
                 // Errors will be printed
					log.Println("error:", err)
				}
			}
		}()

		watcher.Add(configDir)
		<-done
	}()
}

Among them, fsnotify is a third-party library used to monitor directories and files; watcher, err: = fsnotify.NewWatcher() is used to create a new monitoring handler, which opens a protocol to start waiting for events to be read, completes the port read task from I/O, and injects events into the Event object, Watcher.Events;

After executing v.ReadInConfig(), the contents of the configuration file are re-read into the viper instance, as follows:
  

After v.ReadInConfig() is executed, the content of the config field is the latest content modified by the user.

The onConfigChange of this line v.onConfigChange(event) is a property of the core structure Viper, and the type is func:

type Viper struct {
	// Delimiter that separates a list of keys
	// used to access a nested value in one go
	keyDelim string

	// A set of paths to look for the config file in
	configPaths []string

	// The filesystem to read config from.
	fs afero.Fs

	// A set of remote providers to search for the configuration
	remoteProviders []*defaultRemoteProvider

	// Name of file to look for inside the path
	configName string
	configFile string
	configType string
	envPrefix  string

	automaticEnvApplied bool
	envKeyReplacer      *strings.Replacer

	config         map[string]interface{}
	override       map[string]interface{}
	defaults       map[string]interface{}
	kvstore        map[string]interface{}
	pflags         map[string]FlagValue
	env            map[string]string
	aliases        map[string]string
	typeByDefValue bool

	// Store read properties on the object so that we can write back in order with comments.
	// This will only be used if the configuration read is a properties file.
	properties *properties.Properties

	onConfigChange func(fsnotify.Event)
}

It is used to pass in this event to execute the function you write.Why do changes take effect immediately?I believe the second question has been solved.

Next, let's see how OnConfigChange(func(e fsnotify.Event) {...}) works:

func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
	v.onConfigChange = run
}

The method parameter is a function of type func (in fsnotify.Event){}, which means that the developer needs to put his own execution logic into this func and execute the function you write when he listens to the event, so he can write as follows:

	viperInstance.OnConfigChange(func(e fsnotify.Event) {
		log.Print("Config file updated.")
		viperLoadConf(viperInstance)    // The viperLoadConf function is the logic to inject the latest configuration into a custom structure object
	})

The parameters of the OnConfigChange method are assigned to the parameter run and passed to the onConfigChange property of the viper instance to execute the function with the v.onConfigChange(event) in the WatchConfig() method.

At this point, the first question has been solved.

 

Gradually fading away from books, where does the Broadwater fish sink?

155 original articles published. 74% praised. 40,000 visits+
Private letter follow

Posted by php_2004 on Fri, 10 Jan 2020 16:54:52 -0800