Go语言热重载和优雅地关闭程序

这篇具有很好参考价值的文章主要介绍了Go语言热重载和优雅地关闭程序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Go语言热重载和优雅地关闭程序

我们有时会因不同的目的去关闭服务,一种关闭服务是终止操作系统,一种关闭服务是用来更新配置。

我们希望优雅地关闭服务和通过热重载重新加载配置,而这两种方式可以通过信号包来完成。

1、代码实现

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")
	}
}

首先,定义了一个 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.SIGHUP、syscall.SIGINT 和 syscall.SIGTERM 是什么意思?

  • syscall.SIGINT 是用来在 Ctrl+C 时优雅地关闭的,它也相当于 os.Interrupt。
  • syscall.SIGTERM 是常用的终止信号,也是 docker 容器的默认信号,Kubernetes 也使用它。
  • syscall.SIGHUP 用于热重载配置。

如何优雅地关闭,multiSignalHandler(<-sigCh) 被用来接收 chan 值,然后它将决定运行代码的哪一部分。

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")
	}
}

2、测试优雅关闭

首先,运行服务器。

$ go run main.go
2023/06/25 09:57:05 starting up....

发送一个curl请求。

$ curl localhost:8080
Before hot reload

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

$ go run main.go
2023/06/25 09:57:05 starting up....
2023/06/25 09:59:45 Signal: interrupt
2023/06/25 09:59:45 Interrupt by Ctrl+C

3、热重载

首先,运行服务器。

$ go run main.go
2023/06/24 22:03:17 starting up....

然后,发送curl请求。

$ curl localhost:8080
Before hot reload

查看进程:

$ lsof -i tcp:8080
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
main    85193 root    3u  IPv6 13642377      0t0  TCP *:webcache (LISTEN)

使用 kill 杀死进程:

$ kill -SIGHUP 85193
$ go run main.go
2023/06/24 22:03:17 starting up....
2023/06/24 22:06:05 Signal: hangup
2023/06/24 22:06:05 After hot reload

如果直接使用 kill 命令杀死程序:

$ go run main.go
2023/06/24 22:14:11 starting up....

$ lsof -i tcp:8080
COMMAND   PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
main    89619 root    3u  IPv6 13669401      0t0  TCP *:webcache (LISTEN)

$ kill -9 89619
$ go run main.go
2023/06/24 22:14:11 starting up....
2023/06/24 22:14:50 Signal: terminated
2023/06/24 22:14:50 Process is killed.

4、Go信号库os/signal

在官方介绍中,这个库主要封装信号实现对输入信号的访问,信号主要用于类 Unix 系统。

信号是事件发生时对进程的通知机制,有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的

正常流程,大多数情况下,无法预测信号到达的精确时间。

因为一个具有合适权限的进程可以向另一个进程发送信号,这可以称为进程间的一种同步技术。当然,进程也可以

向自身发送信号。然而,发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

  • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一

    条异常的机器语言指令(除0,引用无法访问的内存区域)。

  • 用户键入了能够产生信号的终端特殊字符。如中断字符 (通常是 Control-C)、暂停字符(通常是 Control-Z)。

  • 发生了软件事件。如调整了终端窗口大小,定时器到期等。

4.1 举例说明

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	// os.Signal是一个系统信号接收channel
	c := make(chan os.Signal, 1)
	// syscall都是一些系统信号
	signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
	for {
		s := <-c
		fmt.Printf("get a signal %s", s.String())
		switch s {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			fmt.Printf("exit")
			os.Exit(0)
		case syscall.SIGHUP:
			fmt.Printf("reload")
		default:
			fmt.Printf("nothing")
		}
	}
}

1、首先初始化一个 os.Signal 类型的 channel,我们必须使用缓冲通道,否则在信号发送时如果还没有准备好接

收信号,就有丢失信号的风险。

2、signal.notify 用于监听信号,参数1表示接收信号的 channel,参数2及后面的表示要监听的信号:

  • syscall.SIGHUP 表示终端控制进程结束

  • syscall.SIGQUIT 表示用户发送QUIT字符 (Ctrl+/) 触发

  • syscall.SIGTERM 表示结束进程

  • syscall.SIGINT 表示用户发送INTR字符 (Ctrl+C) 触发

3、<-c 一直阻塞直到接收到信号退出。

对于上面的的程序是优雅的退出守护进程,接下来就是一些释放资源或dump进程当前状态或记录日志的动作,完

成这些后,主进程退出。

4.2 GO的信号类型

4.2.1 POSIX.1-1990标准中定义的信号列表
信号值 动作 说明
SIGHUP 1 Term 终端控制进程结束(终端连接断开)
SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发
SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发
SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT 6 Core 调用abort函数触发
SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等)
SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM 14 Term 时钟定时信号
SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略)
SIGUSR1 30,10,16 Term 用户保留
SIGUSR2 31,12,17 Term 用户保留
SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收)
SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞)
SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略)
SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略)
SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发
SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发
4.2.2 在SUSv2和POSIX.1-2001标准中的信号列表
信号 动作 说明
SIGTRAP 5 Core Trap指令触发(如断点,在调试器中使用)
SIGBUS 0,7,10 Core 非法地址(内存地址对齐错误)
SIGPOLL Term Pollable event (Sys V). Synonym for SIGIO
SIGPROF 27,27,29 Term 性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS 12,31,12 Core 无效的系统调用(SVr4)
SIGURG 16,23,21 Ign 有紧急数据到达Socket(4.2BSD)
SIGVTALRM 26,26,28 Term 虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU 24,24,30 Core 超过CPU时间资源限制(4.2BSD)
SIGXFSZ 25,25,31 Core 超过文件大小资源限制(4.2BSD)

信号 SIGKILL 和 SIGSTOP 可能不会被程序捕获,因此不会受此软件包影响。

同步信号是由程序执行中的错误触发的信号:SIGBUS,SIGFPE 和 SIGSEGV。这些只在程序执行时才被认为是同

步的,而不是在使用 os.Process.Kill 或 kill 程序或类似的机制发送时。一般来说,除了如下所述,Go 程序会将同

步信号转换为运行时异常。其余信号是异步信号,它们不是由程序错误触发的,而是从内核或其他程序发送的。

在异步信号中,SIGHUP 信号在程序失去其控制终端时发送。当控制终端的用户按下中断字符(默认为^ C

(Control-C))时,发送 SIGINT 信号。当控制终端的用户按下退出字符时发送 SIGQUIT 信号,默认为^ \

(Control-Backslash)。一般情况下,您可以通过按^ C来使程序简单地退出,并且可以通过按^使堆栈转储退出。

4.3 Kill命令的原理

我们平时在 Linux 系统会 kill 命令来杀死进程,那其中的原理是什么呢。

4.3.1 kill pid

kill pid 的作用是向进程号为 pid 的进程发送 SIGTERM (这是 kill 默认发送的信号),该信号是一个结束进程的信号

且可以被应用程序捕获。若应用程序没有捕获并响应该信号的逻辑代码,则该信号的默认动作是 kill 掉进程。这是

终止指定进程的推荐做法。

4.3.2 kill -9 pid

kill -9 pid 则是向进程号为 pid 的进程发送 SIGKILL (该信号的编号为9),从本文上面的说明可知,SIGKILL 既不能

被应用程序捕获,也不能被阻塞或忽略,其动作是立即结束指定进程。通俗地说,应用程序根本无法感知 SIGKILL

信号,它在完全无准备的情况下,就被收到 SIGKILL 信号的操作系统给干掉了,显然,在这种暴力情况下,应用

程序完全没有释放当前占用资源的机会。事实上,SIGKILL 信号是直接发给 init 进程的,它收到该信号后,负责终

止 pid 指定的进程。在某些情况下(如进程已经 hang 死,无响应正常信号),就可以使用 kill -9 来结束进程。文章来源地址https://www.toymoban.com/news/detail-798464.html

到了这里,关于Go语言热重载和优雅地关闭程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 掌握Go语言:Go语言范围,优雅遍历数据结构,简化代码操作实战解析(24)

    在Go语言中,范围(range)用于迭代数组、切片、映射、通道等数据结构的元素。范围的语法形式为 for range ,可以遍历集合中的每个元素,并在每次迭代中返回元素的索引(或键)和对应的值。 Go语言范围使用方法 使用范围语句的一般形式为: 其中, index 是元素的索引(或

    2024年04月17日
    浏览(34)
  • 【Go 基础篇】Go语言中的defer和recover:优雅处理错误

    Go语言以其简洁、高效和强大的特性受到了开发者的热烈欢迎。在错误处理方面,Go语言提供了一种优雅的机制,即通过 defer 和 recover 组合来处理恐慌(panic)错误。本文将详细介绍Go语言中的 defer 和 recover 机制,探讨其工作原理和在实际开发中的应用。 在软件开发过程中,

    2024年02月10日
    浏览(30)
  • Golang优雅实现按比例切分流量

    我们在进行灰度发布时,往往需要转发一部分流量到新上线的服务上,进行小规模的验证,随着功能的不断完善,我们也会逐渐增加转发的流量,这就需要按比例去切分流量,那么如何实现流量切分呢? 我们很容易想到通过生成随机数方式进行实现,通过判断生成随机数是否

    2024年02月04日
    浏览(28)
  • 探索 Go 语言中 iota 的神奇力量,优雅定义常量!

    大家好!在今天的文章中,我将为大家介绍一下关于 golang 中的 iota 的使用技巧和注意事项。作为一种枚举常量生成器,iota 在 golang 中被广泛使用,可以简化代码和提高可读性。下面就让我们来了解一些关键的使用技巧和注意事项吧! 在 golang 中,iota 是一个预定义标识符,

    2024年02月13日
    浏览(30)
  • Golang:Go语言结构

    在我们开始学习 Go 编程语言的基础构建模块前,让我们先来了解 Go 语言最简单程序的结构。 Go 语言的基础组成有以下几个部分: 包声明 引入包 函数 变量 语句 表达式 注释 接下来让我们来看下简单的代码,该代码输出了\\\"Hello World!\\\": 让我们来看下以上程序的各个部分: 第一

    2024年02月10日
    浏览(50)
  • golang http服务实现多ip监听,及优雅重启

    在工作中,有时需要对http服务实现多监听,http服务重启等需求。大多数web框架只实现的是单ip监听,要实现多ip监听就需要循环监听ip; 而重启http服务,首先想到的是用endless来优雅的实现服务的重启,但是当多ip监听时,一个项目不能用一个endLess,多了会报错,且windows环境也

    2023年04月12日
    浏览(29)
  • 【Golang】VScode配置Go语言环境

    安装VScode请参考我的上一篇博客:VScode安装_㫪548的博客-CSDN博客 接下来我们直接进入正题: Go语言(又称Golang)是一种开源的编程语言,由Google开发并于2009年首次发布。Go语言具有简洁、高效、可靠和易于阅读的特点,被设计用于解决大型项目的开发需求。它结合了静态类型

    2024年02月03日
    浏览(55)
  • 【GoLang】MAC安装Go语言环境

    小试牛刀 首先安装VScode软件 或者pycharm mac安装brew软件  brew install go 报了一个错误 不提供这个支持  重新brew install go 之后又重新brew reinstall go 使用go version 可以看到go 的版本 使用go env  可以看到go安装后的配置 配置一个环境变量 vim ~/.zshrc,  

    2024年02月15日
    浏览(45)
  • Go语言(Golang)数据库编程

    要想连接到 SQL 数据库,首先需要加载目标数据库的驱动,驱动里面包含着于该数据库交互的逻辑。 sql.Open() 数据库驱动的名称 数据源名称 得到一个指向 sql.DB 这个 struct 的指针 sql.DB 是用来操作数据库的,它代表了0个或者多个底层连接的池,这些连接由sql 包来维护,sql 包会

    2024年02月03日
    浏览(68)
  • 【Golang】Golang进阶系列教程--Go 语言切片是如何扩容的?

    在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。 切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一种引用类型,它有三个属性:指针,长度和容量。 底层源码定义如下: 指针: 指向

    2024年02月14日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包