Go profile hot load-send system signals

  golang

In the development of daily projects, we often use configuration files to save the basic metadata of projects. There are many types of configuration files, such as:JSONxmlyamlOr even a file in plain text format. No matter what type of configuration data it is, in some scenarios, we may have the need to update the contents of the current configuration file hot, such as: a resident process running with Go, running aWeb ServerService process.

At this time, if the configuration file changes, how can we let the current program re-read the contents of the new configuration file? Next, we will update the configuration file in the following two ways:

  1. Use system signals (manual).
  2. Useinotify, listening for file modification events.

Either way, the Go language will be used.goroutineI intend to use the concept ofgoroutineStart a new coordination process. The purpose of the new coordination process is to receive the system signal or monitor the event that the file is modified. If you are rightgoroutineI am not very familiar with the concept of, then I suggest you consult the relevant information first.

Manual, using system signals.

The reason why I call this method manual (Manual), because the file update requires us to manually inform the currently dependent running program: “hey, buddy! The configuration file has been updated, you have to read the configuration again! !” , we told the way is to send a system signal to the currently running program, so the program’s general idea is as follows:

  1. In the Go main process, a new one is started.goroutineTo receive signals.
  2. The new goroutine listens for signals and then updates the configuration file.

In*nixAccording to the regulations in the system,USR1AndUSR2All belong to user-defined signals. As forUSR1AndUSR2Which is more suitable?Wikipedia) also did not give an authoritative answer, so here I according to the convention commonly known as the rules, intend to useUSR1:

If you have used a Web Server such as Nginx or Apache, then you are certainly somewhat impressed by the strategy of signaling to update the configuration file.

Monitor signal

InGoIt is necessary to monitor system signals in languagesignalpackageNotify()Method, the method requires at least two parameters, the first parameter requires a channel of system signal type, and the subsequent parameters are one or more system signals to be monitored:

import "os/signal"

Notify(c chan<- os.Signal, sig ...os.Signal)

Therefore, our code is roughly as follows:

package main
 
 import (
 "os"
 "os/signal"
 "syscall"
 )
 
 func main() {
 //Declare a signal channel with a capacity of 1
 sig := make(chan os.Signal, 1)
 //Monitor the signal from SIGUSR1
 signal.Notify(sig, syscall.SIGUSR1)
 }

Here we created a channel with a signal capacity of 1 (channel), which means that the channel can accommodate at most one signal unit. If a signal unit already exists in the current channel and another signal is received and needs to be sent to the channel, the program will be blocked when sending the signal until the signal in the channel is processed.

In this way, we can accurately process only one signal at a time, and multiple signals need to be queued, which is exactly what I want.

Signal processing

After the system signal is monitored and stored in the channel (sig, then we need to process the received signal, here we have a new routine (goroutine), the purpose of using the routine is to hope that subsequent tasks do not block the operation of the main process, inGOIn the language, another coordination process is very convenient, only need to call the keyword:goJust:

go func(){
//new thread
}()

We hope to obtain the system signals in the channel in the new coordination process without stopping. The code is as follows:

go func() {
for {
select {
case <-sig:
//Get the signal in the channel and process the signal
}
}
}()

GOIn languageselectStatement, whose structure is somewhat similar to that of other languagesswitchStatement, but the difference is,selectCan only be used for processinggoroutineThe communication operation, andgoroutineThe communication is based onchannelTo achieve, so to put it bluntly:selectCan only be used to process channels (channel) operation.

CurrentselectWill remain blocked until one of themcaseThis will only be executed if the conditions are met.caseStatement under the condition. And here we useforCircular structureselectStatement is in an infinite loop, ifselectinferiorcaseAfter receiving a processed signal, when the processing is finished; Because of the outer layerforThe function of the loop statement is equivalent to resettingselectWhen no new signal is received,selectWill again be blocked waiting, cycling.

If you are rightselectStatement blocking in question, we might as well consider the following code operation:

for {
select {
case <-sig:
//Get the signal in the channel and process the signal
}
fmt.Println("select block test!"  )
}

In the aboveselectAfter the statement, we try to output a line of string, then ask, “This linefmt.Println()The function will be displayed in theforRun immediately in the loop? “

The answer is yes: no!selectIt will block the tone and there will be no output when the program runs untilcaseMatch to. it wont hurt you to try it

Hot load configuration

We have already prepared the signal monitoring and the simple work of signal processing. Next we need to refine the code of the signal processing stage and add the logic of loading the configuration file. We will demonstrate loading a simplejsonConfiguration file, the path of the file is stored in/tmp/env.json, the content is relatively simple, only onetestFields:

{
 "test": "D"
 }

At the same time, we need to create and parse thejsonFormat matching data structure:

type configBean struct {
 Test string
 }

We made a statementconfigBeanStructure for andenv.jsonProfile fields are mapped one by one, and then simply calljson.Unmarshal()Function, we can take thisjsonThe contents of the document are converted into corresponding onesGoLanguage structure content, of course, this is not enough, after parsing we also need to declare a variable to store the structure data, for the program to call elsewhere:

//Global Configuration Variables
 var Config config
 
 type config struct {
 LastModify time.Time
 Data       configBean
 }

Here, I did not directly putconfigBeananalyticaljsonData is assigned to a global variable, but is wrapped in another layer and an additional field is declared.LastModifyIt is used to store the last modification time of the current file. This has the advantage that each time we receive a signal that we need to update the configuration file, we also need to compare whether the modification of the current file is greater than the last update time. Of course, this is only a small skill for configuration optimization loading.

The following is our code for loading configuration files, and a new one has been added hereloadConfig(path string)Function to encapsulate all logic for loading configuration files:

//Global Configuration Variables
 var Config *config
 
 type configBean struct {
 Test string
 }
 
 type config struct {
 LastModify time.Time
 Data configBean // configure content store fields
 }
 
 func loadConfig(path string) error {
 var locker = new(sync.RWMutex)
 
 //Read the contents of the configuration file
 data, err := ioutil.ReadFile(path)
 if err !  = nil {
 return err
 }
 
 //Read File Properties
 fileInfo, err := os.Stat(path)
 if err !  = nil {
 return err
 }
 
 //Verify the modification time of the file
 if Config !  = nil && fileInfo.ModTime().Before(Config.LastModify) {
 return errors.New("no need update")
 }
 
 //parse file content
 var configBean configBean
 err = json.Unmarshal(data, &configBean)
 if err !  = nil {
 return err
 }
 
 config := config{
 LastModify: fileInfo.ModTime(),
 Data:       configBean,
 }
 
 //Reassign and update the configuration file
 locker.Lock()
 Config = config
 locker.Unlock()
 
 return nil
 }

AboutloadConfig()Function we need to explain is that although we use locks here, we do not use locks in file reading and writing, only in the assignment phase, because there is no more than one in this scenariogoroutineNeed to operate the same file at the same time, if there are multiple scenes in your scenegoroutineConcurrent write operations, then to be on the safe side, it is recommended that you add a lock mechanism to the reading and writing of files.

At this point, we have basically completed all the logic for updating the configuration file with the monitoring system signal. Next, we will demonstrate the final result. Before the demonstration, we need tomainFunction to add a little extra code, simulate the main process to become a resident process, here still use channel, the final code is roughly as follows:

func main() {
configPath := "/tmp/env.json"
done := make(chan bool, 1)

//Define Signal Channels
sig := make(chan os.Signal, 1)

signal.Notify(sig, syscall.SIGUSR1)

go func(path string) {
for {
select {
case <-sig:
//Receive the signal and load the configuration file.
_ := loadConfig(path)
}
}
}(configPath)

//Suspend the process until a signal is obtained
<-done
}

Finally, we use a gif picture to demonstrate the final effect:

The final full version code is available here:Github code addressAnd it should be noted that,demoThere are still some minor details in the code, such as: error handling, signal channel closing, etc., please handle it yourself.

Warning: considering the length of the article, we have only implemented the first file update method in this article. In the next article, we will use the second method: usinginotifyMonitor the changes of configuration files to realize automatic update of configuration files. I look forward to your attention.

(360 technical original content, reprint please be sure to keep the two-dimensional code at the end of the article, thank you ~)

图片描述

About 360 Technology

360 technology is a technology sharing public number created by 360 technology team, pushing technology dry goods every day.

For more technical information, please pay attention to “360 technology” WeChat public number