原来服务端的退出姿势也可以这么优雅

news2024/12/29 11:01:14

最简单的 http 服务端

咱们来写一个简单的 http 服务器

func main() {
 srvMux := http.NewServeMux()
 srvMux.HandleFunc("/getinfo", getinfo)
 http.ListenAndServe(":9090", srvMux)

}

func getinfo(w http.ResponseWriter, r *http.Request) {
 fmt.Println("i am xiaomotong!!!")
 w.Write([]byte("you are access right!!\n"))
}

这个功能非常简单,就是监听了本地的 9090 端口,并且其中有一个 url 是会处理请求的,/getinfo ,咱们可以通过如下指令来请求一下看看效果

# curl localhost:9090/getinfo
you are access right!!

明确是可以正常访问的,且也会拿到我们对应的信息,服务器的日志也是正常的

咱们思考一下,这个时候如果遇到了意外,程序崩溃了,panic 了,或者我们认为的 kill 掉了,我们如何判断服务端是如何退出的呢?

加入 信号的 服务端

我们写 C/C++ 的时候对于信号应该不陌生吧,在 golang 里面,我们也加入信号来识别是否是认为 kill 程序的

linux 里面可以通过 man kill 查看 kill 指令的详细说明

这里我们可以看到一个kill -9 是对应的 SIGKILL 信号 ,我们还知道 SIGINT 信号是 Ctrl-C 的时候会发出这个信号,也是一个中断信号,如果对于这点不清楚话,可以网络上搜索一下 linux 信号列表

func main() {
 sig := make(chan os.Signal)
 signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

 srvMux := http.NewServeMux()
 srvMux.HandleFunc("/getinfo", getinfo)
 go http.ListenAndServe(":9090", srvMux)

  fmt.Println(<-sig)
}

http.ListenAndServe 是阻塞的,则此处咱们监听 9090 的服务是开了一个单独处理

验证一下

# go run main.go
^Cinterrupt

这个时候,我们的 http 服务器,已经能够区分信号了,知道自己是如何退出的了

咱们的需求有慢慢的增加,实际工作中,肯定不能做的这么 cuo

优雅的退出

工作中,我们带有 http 的服务端,肯定还有别的处理逻辑,例如读写文件,GRPC 通信,或者是使用数据库,那么我们程序关闭情况,还是要根据情况来处理,要遵循原子性

有如下 2 种情况:

  • 对于数据没有严格的质量要求,程序 panic 也无所谓,那么这个时候不用优雅关闭也没有啥问题
  • 对于上述说到的会操作数据库,读写文件等等会修改数据的,这里可不期望操作数据的过程中被中断, 我们要遵循原子性,咱们的程序需要提供一个缓冲的时间,来优雅的退出

正常工作中退出必须是优雅的

如何实现优雅退出呢?

例如上面的例子,当主协程收到了中断信号后,就会马上退出程序,子协程也会相应退出

如果需要主协程等待子协程处理完当前手里的活再退出,那么我们是不是需要让主协程和子协程相互通信,才有可能实现呢?

使用 2 个 channel 来实现优雅关闭

这个方法比较容易想到

实现大体分为 2 步走:

  • 主协程收到中断信号后,通知子协程优雅关闭 ,这里命名为 stopCh
  • 子协程收到通知后,处理完手头的通知主协程关闭程序,这里命名为 closeCh
func main() {
 sig := make(chan os.Signal)
 signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

 stopCh := make(chan int)
 closeCh := make(chan int)

 srvMux := http.NewServeMux()
 srvMux.HandleFunc("/getinfo", getinfo)
 go http.ListenAndServe(":19999", srvMux)

 go func(stopCh, closeCh chan int) {
  for {
   select {
   case <-stopCh:
    fmt.Println(" processing tasks")
                // 模拟正在处理数据
    time.Sleep(time.Second*time.Duration(2))
    fmt.Println("process over ")
    closeCh <- 1
   }
  }
 }(stopCh, closeCh)

 <-sig
 stopCh <- 1
 <-closeCh
 fmt.Println("close server ")
}

此处我们可以看出使用了 2 个通道来让主协程和子协程相互通信

开辟一个协程,执行匿名函数来监听 stopCh 通道是否有数据,若有数据,说明主协程收到了信号,并且通知子协程要优雅关闭了

这个时候,子协程做完自己的事情,就在 closeCh 写入数据,通知主协程可以正常关闭程序了

使用嵌套的 channel 来实现

使用 嵌套的 channel 来实现优雅关闭,可能一下子还想不到,不过官网有给我们一些方向

实现思路是:

  • 使用一个通道 stopCh,通道 stopCh 里面的元素是另外一个通道 tmpCh
  • 当主协程收到退出信号时,在 stopCh 中写入数据 tmpCh,并开始监听 tmpCh 是否有数据
  • 子协程从 stopCh 读取到数据 tmpCh 时,便知道自己需要优雅关闭了,处理完自己的事情之后,子协程往 tmpCh 写入数据
  • 主协程监听到 tmpCh 有数据,则退出程序
func main() {
 sig := make(chan os.Signal)
 signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

 stopCh := make(chan chan struct{})

 srvMux := http.NewServeMux()
 srvMux.HandleFunc("/getinfo", getinfo)
 go http.ListenAndServe(":19999", srvMux)

 go func(stopCh chan chan struct{}) {
  for {
   select {
   case tmpCh:=<-stopCh:
    fmt.Println(" processing tasks")
    time.Sleep(time.Second*time.Duration(2))
    fmt.Println("process over ")
    tmpCh <- struct{}{}
   }
  }
 }(stopCh)

 tmpCh := make(chan struct{})

 <-sig
 stopCh <- tmpCh
 <-tmpCh
 fmt.Println("close server ")
}

上面 2 种方法都比较类似,都是使用通道来实现优雅关闭的功能,通道是 golang 天生的数据结构,咱们要用起来

使用 golang 标准解法 context

使用 golang 的 context ,能够更好的实现优雅关闭的问题

别以为 context 只会拿来传递数据,context 也是可以控制 子协程的生命周期的

func main() {
 sig := make(chan os.Signal)
 signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

 stopCh := make(chan struct{})
    // 创建一个上下文
 ctx,cancle := context.WithCancel(context.Background())

 srvMux := http.NewServeMux()
 srvMux.HandleFunc("/getinfo", getinfo)
 go http.ListenAndServe(":19999", srvMux)

 go func(ctx context.Context,stopCh chan struct{}) {
  for {
   select {
   case <-ctx.Done():
    fmt.Println(" processing tasks")
    time.Sleep(time.Second*time.Duration(2))
    fmt.Println("process over ")
    stopCh <- struct{}{}
   }
  }
 }(ctx,stopCh)

 <-sig
 cancle()
 <-stopCh
 fmt.Println("close server ")
}

此处我们使用 context 的方式,当主协程关闭上下文的时候,子协程就会从通道到读取到数据,进而进行优雅关闭,我们可以看到源码,ctx.Done() 的返回值也是一个通道

主协程等待所有子协程优雅关闭实现方法

上面我们说到的都是主协程等待 1 个子协程优雅关闭后,自己关闭程序

那么实际工作中肯定是不止一个协程的,咱们要做的优雅,那就优雅到底 ,此处我们的处理方式是 golang 中 context + sync.WaitGroup 的方式来实现

func main() {
 sig := make(chan os.Signal)
 signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)
 ctx, cancle := context.WithCancel(context.Background())

 mywg := sync.WaitGroup{}
 // 控制 5 个子协程的声明周期
 mywg.Add(5)

 for i := 0; i < 5; i++ {
  go func(ctx context.Context) {
   defer mywg.Done()
   for {
    select {
    case <-ctx.Done():
     fmt.Println(" processing tasks")
     time.Sleep(time.Second * time.Duration(1))
     fmt.Println("process over ")
     return

    default:
     time.Sleep(time.Second * time.Duration(1))
    }
   }
  }(ctx)
 }

 <-sig
 cancle()
 // 等待所有的子协程都优雅关闭
 mywg.Wait()
 fmt.Println("close server ")
}

上述代码中,我们使用 sync.WaitGroup 控制 5 个子协程的生命周期,当主协程收到中断信号后,cancle() 掉 ctx

每一个子协程都能从 ctx.Done() 读取到数据,自行处理完毕手中事情后

最终 defer mywg.Done() ,主协程 mywg.Wait() 等待所有协程都优雅关闭后,自己也关闭了自己的程序

验证效果

# go run main.go
^C processing tasks
processing tasks
processing tasks
processing tasks
processing tasks
process over
process over
process over
process over
process over
close server

以上就是从一个不会优雅关闭到学会常用优雅关闭方法的简单路径,希望对你有用哦

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序猿入门|编程注重写注释,代码规范注释有哪些讲究?

注释风格 1.总述 一般使用 // 或 /* */,只要统一就好。 2.说明 // 或 /* */ 都可以,但 // 更 常用,要在如何注释及注释风格上确保统一。 文件注释 1.总述 在每一个文件开头加入版权、作者、时间等描述。 文件注释描述了该文件的内容,如果一个文件只声明,或实现,或测试…

JVM运行时参数

3.类型三&#xff1a;-XX参数选项 特点 作用 用于开发和调试jvm 分类 特别地 二、添加jvm参数选项 1.运行jar包 2.通过Tomcat运行war包 3.程序运行过程中 三、常用的JVM参数选项 1.打印设置的XX选项及值 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210219105124…

window安装torch环境--不踩坑须知!

window安装torch环境–不踩坑须知&#xff01; 1. 查看电脑安装的cudn版本 进入cmd输入&#xff1a; nvidia-smi2.查找cuda对应的torch版本 在官网查找&#xff1a;https://pytorch.org/get-started/previous-versions/ 如果呢你在新建的conda环境里输入该命令&#xff0c;…

Java基于JSP的网络音乐KTV点歌电台网站

随着我国网民的增加,也促进了网络音乐电台的开发。随着网络技术的发展,人们在利用网络学习的同时,也在享受着网络带来的各种附带产品所产生的效应,如网络游戏,网络歌曲。网络音乐电台正是在这样的需求前提下应运而生,给人们的日常生活带来了极大的乐趣,让人们在繁忙疲惫的工作之…

TensorRT从理论到实践

TensorRT理论 一. TensorRT介绍 TensorRT是一个高性能的深度学习推理优化器&#xff0c;可以为深度学习应用提供低延迟、高吞吐率的部署推理。TensorRT可用于对超大规模数据中心、嵌入式平台或自动驾驶平台进行推理加速。TensorRT现已能支持Tensorflow、Caffe、Mxnet、Pytorc…

《嵌入式基础》实验三 ARM编程模型和ARM指令

零、前言 本人不擅长写汇编相关的东西&#xff0c;所以以下内容也是不断摸索&#xff08;百度 &#xff09; 整出来的&#xff0c;和linux的实验报告的质量相比较低。 一、 实验目的 掌握ARM微处理器的汇编指令的使用方法。掌握使用 LDM/STM&#xff0c;B&#xff0c;BL 等指…

Struts、Struts2、Spring MVC、JSF、AngularJS、React以及Vue的对比

Struts是一种Java语言的Web应用框架&#xff0c;用于构建基于Java的Web应用程序。它旨在为开发人员提供一种简单易用的方法来构建动态Web页面。Struts框架提供了一组组件&#xff0c;用于处理常见的Web应用程序任务&#xff0c;包括处理用户输入&#xff0c;验证用户输入&#…

(算法设计与分析)第七章随机化算法概述

文章目录一&#xff1a;概述&#xff08;1&#xff09;什么是随机化算法&#xff08;2&#xff09;随机化算法的特点&#xff08;3&#xff09;随机化算法分类&#xff08;4&#xff09;随机数二&#xff1a;数值随机化算法&#xff08;以计算πππ值为例&#xff09;三&#…

分布式计算 MapReduce 究竟是怎么一回事?

前言 如果要对文件中的内容进行统计&#xff0c;大家觉得怎么做呢&#xff1f;一般的思路都是将不同地方的文件数据读取到内存中&#xff0c;最后集中进行统计。如果数据量少还好&#xff0c;但是面对海量数据、大数据的场景这样真的合适吗&#xff1f;不合适的话&#xff0c;…

操作系统装完之后,安装几个特别有用的经典软件,都是电脑必备,包含pdf编辑、图片编辑、wiki、压缩、影音等等

操作系统装完之后&#xff0c;安装几个特别有用的经典软件&#xff0c;都是电脑必备&#xff0c;包含pdf编辑、图片编辑、wiki、压缩、影音等等。 Gimp https://www.gimp.org/ Gimp 是一款小巧实用的图片编辑工具。 如果你不想用笨重的PS&#xff0c;那可以尝试一下Gimp&…

元胞自动机模拟病毒传染(SEIR模型)(Python代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

SSM整合-如何配置相关文件

下述操作都是在IDEA上进行 1.首先新建一个Maven工程。 2.在pom.xml中增加相关依赖 <properties><spring.version>5.3.1</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId>&l…

安卓玩机搞机技巧综合资源----手机各种代码 查询信息 开启端口 调试选项【十】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

踩坑Xxljob本地部署后调度一半成功一半失败原因分析及解决方案记录

缘由 入门学习和本地部署Xxljob过程中&#xff0c;发现Xxljob任务一半调度成功&#xff0c;一半调度失败&#xff0c;给我邮箱发爆了&#xff0c;为啥呢&#xff1f;查了半天资料都没解决 成功比例图&#xff1a; 实际操作时&#xff0c;发现单次手动执行一定成功&#xff0…

Python之数据库编程

目录 一、MySQL数据库的使用 数据库相关操作 二、数据库增删改查 增加 修改 删除 三、数据库标准写法 一、MySQL数据库的使用 建表 CREATE TABLE py_student( id INTEGER primary key auto_increment, name INTEGER not null, gender varchar(11) default 男 , birthday d…

CMake中define_property的使用

CMake中的define_property命令用于定义和记录自定义属性&#xff0c;其格式如下&#xff1a; define_property(<GLOBAL | DIRECTORY | TARGET | SOURCE |TEST | VARIABLE | CACHED_VARIABLE>PROPERTY <name> [INHERITED][BRIEF_DOCS <brief-doc> [docs...]]…

php+vue基于微信小程序的在线挂号预约小程序

网络的广泛应用给生活带来了十分的便利。所以把在线挂号管理与现在网络相结合&#xff0c;利用ThinkPHP5技术建设在线挂号微信小程序&#xff0c;实现在线挂号的信息化。则对于进一步提高在线挂号管理发展&#xff0c;丰富在线挂号管理经验能起到不少的促进作用。 在线挂号微信…

在飞书搞了个机器人,我让ChatGPT帮忙写算法

一、前言 环境&#xff1a; 系统&#xff1a;Windows 11 64位 Python版本&#xff1a;Python 3.9 注&#xff1a;本文不讲怎么实现&#xff0c;只讲实现的效果和一些思考。大家感兴趣再考虑去配置相关机器人。 先来问问ChatGPT两个问题&#xff1a; 1、ChatGPT是什么&#xff…

Java面试题总结-面向对象

面试题总结第一篇面向对象面向对象和面向过程的区别面向对象三大特性Java是如何实现多态的面向对象 面向对象和面向过程的区别 面向过程&#xff1a; 优点&#xff1a;性能比面向对象高&#xff0c;因为类调用时需要实例化&#xff0c;开销比较大&#xff0c;比较消耗资源;比如…

非零基础自学计算机操作系统 第1章 操作系统概述 1.2 操作系统的历史 1.2.1 操作系统的产生

非零基础自学计算机操作系统 文章目录非零基础自学计算机操作系统第1章 操作系统概述1.2 操作系统的历史1.2.1 操作系统的产生第1章 操作系统概述 1.2 操作系统的历史 由于操作系统是直接建造于硬件层之上的&#xff0c;它的演变必然与计算机系统结构的演变有着密切的联系。 …