Golang热重载和优雅地关闭

简介: Golang热重载和优雅地关闭

我们因不同的目的去关闭服务。有时,关闭服务器的主要目的是用来更新配置。在Golang中,有很多关于优雅关机的帖子。然而,我发现他们并没有提到热重载配置。一般来说,shutdown graceful和hot reload是由信号控制的。这就是我把它们放在一个帖子里的原因。

起初,一个服务器由于各种原因需要关闭,常见的是操作系统的中断,我们希望能优雅地关闭服务器。最后也是最基本的是如果一个应用程序需要更新,只需要更新配置。这种情况总是发生。那个时候,如果你不需要重启服务器,那将是非常惊人的。你只需要更新你的配置,然后发送一个信号。之后,服务器将热重新加载,你的新配置就会工作。上述场景可以在Golang中使用信号包来完成。让我们动手吧。

代码实现

首先,我定义了一个Config结构并声明了一个conf变量。其代码如下:

type Config struct {
 Message string
}
var conf = &Config{Message: "Before hot reload"}

上面的代码只是一个简单的配置样本,你可以根据自己的需要定义一个复杂的结构。

其次,定义一个路由器函数,用来绑定和监听8080端口。在热重载配置完成后,它也被用来显示结果。代码如下:

func router() {
 log.Println("starting up....")
 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  _, _ = w.Write([]byte(conf.Message))
 })
 go func() {
  log.Fatal(http.ListenAndServe(":8080", nil))
 }()
}

下一步是服务器关机和热重载配置。当一个服务器关闭时,它应该停止接收新的请求,同时完成正在进行的请求,返回其响应,然后关闭。像其他文章一样,我将在这里使用信号包。

sigCh := make(chan os.Signal, 1)

之后,我也会使用signal.Notify()。但是我一起发送更多的信号。

signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)

上述代码中,当程序被中断时,signal.Notify将向sigCh通道发送一个信号。

syscall.SIGHUPsyscall.SIGINTsyscall.SIGTERM是什么意思?

syscall.SIGINT是用来在Ctrl+C时优雅地关闭的,它也相当于os.Interrupt

syscall.SIGTERM是常用的终止信号,也是docker容器的默认信号,Kubernetes也使用它。

syscall.SIGHUP用于热重载配置。

这一部分的全部代码如下:

func main() {
   router()
   sigCh := make(chan os.Signal, 1)
   signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
   for {
      multiSignalHandler(<-sigCh)
   }
}

由于这个帖子也包括优雅地关闭,multiSignalHandler(<-sigCh)被用来接收chan值,然后,它将决定运行代码的哪一部分。multiSignalHandler的代码如下:

func multiSignalHandler(signal os.Signal) {
   switch signal {
   case syscall.SIGHUP:
      log.Println("Signal:", signal.String())
      log.Println("After hot reload")
      conf.Message = "Hot reload has been finished."
   case syscall.SIGINT:
      log.Println("Signal:", signal.String())
      log.Println("Interrupt by Ctrl+C")
      os.Exit(0)
   case syscall.SIGTERM:
      log.Println("Signal:", signal.String())
      log.Println("Process is killed.")
      os.Exit(0)
   default:
      log.Println("Unhandled/unknown signal")
   }
}

以上所有的代码将使优雅地关机和热重载配置工作。我把整个代码粘贴在下面,你可以很容易地把它粘贴到你的环境中。

package main
import (
  "log"
  "net/http"
  "os"
  "os/signal"
  "syscall"
)
type Config struct {
  Message string
}
var conf = &Config{Message: "Before hot reload"}
func router() {
  log.Println("starting up....")
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    _, _ = w.Write([]byte(conf.Message))
  })
  go func() {
    log.Fatal(http.ListenAndServe(":8080", nil))
  }()
}
func main() {
  router()
  sigCh := make(chan os.Signal, 1)
  signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
  for {
    multiSignalHandler(<-sigCh)
  }
}
func multiSignalHandler(signal os.Signal) {
  switch signal {
  case syscall.SIGHUP:
    log.Println("Signal:", signal.String())
    log.Println("After hot reload")
    conf.Message = "Hot reload has been finished."
  case syscall.SIGINT:
    log.Println("Signal:", signal.String())
    log.Println("Interrupt by Ctrl+C")
    os.Exit(0)
  case syscall.SIGTERM:
    log.Println("Signal:", signal.String())
    log.Println("Process is killed.")
    os.Exit(0)
  default:
    log.Println("Unhandled/unknown signal")
  }
}

测试优雅关闭和热重载配置


这与之前使用Golang的自动测试不同,此时我手动进行测试。这个测试需要打开两个终端窗口。


测试优雅关闭


首先,在终端一中运行服务器。如果你按上述步骤操作,你会看到与运行服务器后的截图类似的结果:



第二,在第二终端发送一个curl请求。


curl localhost:8080


如果你按上述步骤操作,运行服务器后,你会看到与截图类似的结果。



上面的截图意味着服务器在工作。


让我们先用Ctrl+C测试一下中断。


第三,回到第一终端,然后,用键盘输入Ctrl+C。你会看到下面类似的屏幕截图。



从下面的代码中输出 "通过Ctrl+C中断",你可以在函数multiSignalHandler()中轻松找到它们。


热重载


首先,由于服务器已经被优雅地关闭了,我们应该在终端一中再次运行服务器。


第二,让我们到第二终端。发送curl请求,以确保服务器工作。


第三,在第二终端,运行


lsof -i tcp:port-number


上面的命令是用来检查当前端口号在本地的PID。



第四,在第二终端,运行


kill -SIGHUP 43587


43587与截图中的PID相同。


第五,回到第一终端,如果你按部就班,你会看到类似下面的截图。



现在你会看到 "After hot reload "已经在终端一打印出来了。让我们回到终端二,发送一个 curl 请求来检查结果。



如截图所示,我们知道配置已经被热重载。


在完成上述所有工作后,我们可以用两种方式来终止程序。一种方式,在终端一中使用 "Ctrl+C "来做。另一种方式,使用


kill -9 port-number


在二号终端中,终止程序。

相关文章
|
3月前
|
Kubernetes Go 网络架构
Golang热重载和优雅地关闭
Golang热重载和优雅地关闭
Golang:使用air实现gin应用的live reload热重载
Golang:使用air实现gin应用的live reload热重载
382 0
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
98 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
62 4
Golang语言文件操作快速入门篇
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
92 3
Golang语言之gRPC程序设计示例
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
80 4
|
2月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
45 3
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
46 4
Golang语言goroutine协程篇
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
46 3
Golang语言之Prometheus的日志模块使用案例
|
2月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
47 3
Golang语言之函数(func)进阶篇