Go语言热重载和优雅地关闭程序
Go语言热重载和优雅地关闭程序
我们有时会因不同的目的去关闭服务,一种关闭服务是终止操作系统,一种关闭服务是用来更新配置。
我们希望优雅地关闭服务和通过热重载重新加载配置,而这两种方式可以通过信号包来完成。
1、代码实现
package mainimport ("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 mainimport ("fmt""os""os/signal""syscall"
)func main() {// os.Signal是一个系统信号接收channelc := make(chan os.Signal, 1)// syscall都是一些系统信号signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)for {s := <-cfmt.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 来结束进程。
相关文章:
Go语言热重载和优雅地关闭程序
Go语言热重载和优雅地关闭程序 我们有时会因不同的目的去关闭服务,一种关闭服务是终止操作系统,一种关闭服务是用来更新配置。 我们希望优雅地关闭服务和通过热重载重新加载配置,而这两种方式可以通过信号包来完成。 1、代码实现 package…...
Python实现两个列表相加的方法汇总
1. 使用 “” 运算符 通过 “” 运算符将两个列表相加,得到一个新的列表。例如: list1 [1, 2, 3] list2 [4, 5, 6] result list1 list2 print(result) # [1, 2, 3, 4, 5, 6]2. 使用 extend 方法 使用 extend 方法将一个列表中的元素逐个添加到另…...

debian12.4配置
文章目录 debian12.4配置概述笔记将非root用户添加到sudo组更换国内源配置ssh的客户端访问END debian12.4配置 概述 在虚拟机中装了一个debian12.4, 想配置ssh客户端连接, 出了问题. 配置乱了, 还好长了个心眼, 做了快照. 发现2个问题: debian12.4默认安装完, 有ssh, 先检查…...
linux切换root用户su - root和su root的区别
这里说一下login shell和 no login shell的区别 通过tty客户端登陆的shell就是login shell,通过在图形界面使用ctrlshiftt的方式新建的shell是no login shell login shell 主要读取两个配置文件/etc/profile和~/.bash_profile no login shell 读取的文件和顺序为&am…...

SQL Server Management Studio创建数据表
文章目录 一、建表注意事项1.1 数据类型1.2 建立数据表的基本SQL语法 二、实例说明2.1 创建数据表2.2 实例2 三、标识列和主键示例: 一、建表注意事项 1.1 数据类型 可以看这个去了解数据类型: 1.2 建立数据表的基本SQL语法 建立数据表的基本 SQL 语…...

【AI的未来 - AI Agent系列】【MetaGPT】4.1 细说我在ActionNode实战中踩的那些坑
文章目录 1. MetaGPT 0.5.2 版本的坑1.1 坑一:cannot import name "ActionNode" from "metagpt.actions.action"1.2 坑二:simple_fill 没有参数 schema1.3 坑三:ActionNode一直在循环执行, 2. 升级成 MetaGP…...
Android学习(五):常用控件
Android学习(五):常用控件 常用控件 TextViewEditTextButtonRadioButtonImageView 1、TextView控件 1.1、简介 TextView是用于显示文字(字符串)的控件,可在代码中通过设置属性改变文字的大小、颜色、样式等功能。 1.2、示例…...

基于YOLOv8的学生课堂行为检测,引入BRA注意力和Shape IoU改进提升检测能力
💡💡💡本文摘要:介绍了学生课堂行为检测,并使用YOLOv8进行训练模型,以及引入BRA注意力和最新的Shape IoU提升检测能力 1.SCB介绍 摘要:利用深度学习方法自动检测学生的课堂行为是分析学生课堂表…...

【前后端分离与不分离的区别】
Web 应用的开发主要有两种模式: 前后端不分离 前后端分离 理解它们的区别有助于我们进行对应产品的测试工作。 前后端不分离 在早期,Web 应用开发主要采用前后端不分离的方式,它是以后端直接渲染模板完成响应为主的一种开发模式。以前后端不…...

ubuntu-20.04.6-live-server-amd64安装教程-完整版
简介 Ubuntu 20.04.6 Live Server AMD64 安装教程 - 完整版" 提供了详细的指南,旨在帮助用户在使用 AMD64 架构的服务器上安装 Ubuntu 20.04.6 Live Server 版本。该教程包含全面的步骤和详细说明,使用户能够顺利完成整个安装过程,建立…...

C for Graphic:Sliced Circle Image
不做UI不知道,没想到时至今日,ugui居然没有sliced filled image模式,用circle做filled,不能用sliced九宫格图,导致每次使用这个效果必须一张新图,何其浪费资源。 原始功能如下: 我…...
入门级的 DataV 教程,适用于 Vue 2
入门级的 DataV 教程,适用于 Vue 2。这个教程将指导您创建一个名为 datav-project 的 Vue 项目,并展示如何在其中使用 DataV。我们将从安装 Vue CLI 开始,然后创建项目,接着添加 DataV,并最后显示一个简单的数据可视化…...

JVM工作原理与实战(二十一):内存管理
专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、不同语言的内存管理 1.C/C的内存管理 2.Java的内存管理 二、垃圾回收的对比 1.自动垃圾回收与手动垃圾回收的对比 2.优点与缺点 总结 前言 JVM作为Java程序的运行环境&#…...

Win10 打开文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样
环境: Win10 专业版 加密客户端环境 问题描述: Win10 打开桌面word文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样,只有蓝色圈变大没有鼠标指针出现圈卡着不会动,和那些有鼠标箭头加小蓝色圈不一样 解决方案: 某网上查看的,还是要自己排查…...

论文阅读笔记AI篇 —— Transformer模型理论+实战 (四)
论文阅读笔记AI篇 —— Transformer模型理论实战 (四) 一、理论1.1 理论研读1.2 什么是AI Agent? 二、实战2.1 先导知识2.1.1 tensor的创建与使用2.1.2 PyTorch的模块2.1.2.1 torch.nn.Module类的继承与使用2.1.2.2 torch.nn.Linear类 2.2 Transformer代…...
Template -- Vue2
Vue2 版本 Node 14.14.0Npm 6.14.8Vue vue/cli 5.0.3 npm install -g vue/cli5.0.3 cnpm cnpm7.1.0 npm install -g cnpm7.1.0 --registryhttps://registry.npm.taobao.org 项目 创建 vue create single # vue 2.6.14配置 // vue.config.js const { defineConfi…...

zookeeper window 安装
下载 Apache ZooKeeper 解压Zookeeper安装包到指定目录,注意目录不要有空格。 备份zoo_sample.cfg并改名zoo.cfg 注意:此处的路径一定要使用双斜杠" \\ " D:\\apache-zookeeper-3.8.3-bin\\data 新建环境变量:ZOOKEEPER_HOME D…...

Redis 面试题 | 02.精选Redis高频面试题
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

大数据开发之kafka(完整版)
第 1 章:Kafka概述 1.1 定义 Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。 发布/订阅:消息的发布者不会将消息直接发送给特定的订阅者,而是将发布的消息分为不同的类别,订阅者只…...

单体架构、微服务和无服务器架构
前言 在这篇文章中,我将演示在决定使用单体架构、微服务架构和无服务器架构时的权衡的简化心智模型。目标是突显每种风格的固有优势和缺陷,并提供关于何时选择哪种架构风格的指导。 单体架构 对于小团队或项目来说是理想的入门架构。它简单易上手&…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...