go 服务器优雅的退出 与 重启

  • 简介
  • 说明
  • 优雅的退出
  • 完整测试代码
    • logger.go
    • main.go
    • route.go
    • controller.go

简介

在服务器开发的时候,往往都会有一些关于服务器关闭、服务器重启之类的问题出现。这里简单介绍了 go服务器 收到signal信号之后的关闭、重启操作 —— 有不足的地方以后补充

参考:

优雅退出在Golang中的实现 (qq.com) - 朋友的公众号,可以点点关注,会不定时的分享go的一些相关知识
Go语言WEB框架(Gin)详解

说明

go 中实现优雅的退出,主要使用了 os/signal 包,让程序能够接收到信号,并对信号执行一些操作。比如在linux下,也可以对一些信号进行捕捉,甚至改变信号所对应的行为。

退出部分的代码在退出标题里,其它代码只是我的一个测试环境,可以忽略。

这里我使用的是gin框架,并在gin框架中引入了os/signal包,实现对信号的处理(只做了部分信号的处理)。
并且有些信号不能被捕获,最常见的就是 kill -9 强杀,具体请看下最常见的信号列表。

优雅的退出

func (p *router) Run(host, port string) {//TODO 优化zap log日志,修改成自定义的zap 的logger//err := p.engine.Run(host + ":" + port)//if err != nil {//   //路由启动失败,终止程序//  panic(err)//}//下面所有代码(go1.8+)是为了优雅处理重启等动作srv := &http.Server{Addr:         host + ":" + port,Handler:      p.engine,ReadTimeout:  constant.ReadTimeoutT * time.Second,WriteTimeout: constant.WriteTimeoutT * time.Second,}go func() {// 监听请求if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("listen: %s\n", err)}}()// 优雅Shutdown(或重启)服务// 有一点需要注意的是:这里设置的重启/关闭条件是 收到kill信号,即程序意外终止// 假如,我在这里用一个go携程,让它在2秒之后,panic程序,并且不进行recover恢复程序,那么panic会给进程发送一个os.Exit(2)(源码中可以看),// 这个是会被认为 程序主动退出 ,而非被kill 也就是进程不会收到 kill信号,也就无法重启/关闭了 (虽然panic让程序崩溃了)// 当注释掉下方go程中的defer的时候,程序直接panic,而不重启,反之则可以重启//go func() {//  defer Recover()//// time.Sleep(2 * time.Second)//   panic("dangerous")//}()quit := make(chan os.Signal)signal.Notify(quit, os.Interrupt) // syscall.SIGKILL<-quitlogger.Info("Shutdown Server...")// 10秒后,关闭context-即服务器于10秒后重启,或关闭(gin中的Context,也就是说客户端发送的一次请求,如果在10秒内没处理完,那么这个ctx将会失效,即无法在服务器重启前,成功处理客户端的请求)//TODO 设置一个context失效时间ctx, cancel := context.WithTimeout(context.Background(), constant.ServerRestart*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil {logger.Fatalf("Server Shutdown: ", err)}select {case <-ctx.Done():}logger.Info("Server exiting...")
}func Recover() {if p := recover(); p != nil {fmt.Println("p = ", p)}
}

完整测试代码

logger.go

封装的一个zaplog的包,可以看我另一篇博客
go zap日志库的使用,以及封装

main.go

package mainimport ("context""vsavefile/internal/db""vsavefile/internal/logger""vsavefile/internal/route""vsavefile/pkg/config"
)type Student struct {Age  int32Name string
}//
// ConfigInit
//  @author: xulc
//  @Description: db.InitDBCli 要打印日志,所以依赖Logger的初始化
//
func ConfigInit() {// Logger initlogger.InitLogger()// mongodb cli initdb.InitDBCli()
}// DBInsert test insert data into database
func DBInsert() {s1 := Student{27, "gousheng"}insertResult, err := db.Collection.InsertOne(context.TODO(), s1)if err != nil {logger.Errorf("error inserting student: %v", err)return}logger.Infof("inserted a singnle document: %v", insertResult)
}func main() {ConfigInit()// test for db conn and insert dataDBInsert()defer Disconnect()host := config.File.MustValue("http_server", "host", "127.0.0.1")port := config.File.MustValue("http_server", "port", "8888")route.Router.InitRoute()route.Router.AddRoute()route.Router.Run(host, port)//logger.InitLogger()//defer logger.Logger.Sync()}//
// Disconnect
//  @author: xulc
//  @Description: 执行断开,关闭操作   断开连接,关闭缓冲区
// TODO 程序panic了,defer Disconnect()能否执行?
//
func Disconnect() {// 断开数据库连接err := db.MongoClient.Disconnect(context.TODO())if err != nil {logger.Errorf("error disconnected: %v", err)}logger.Info("db.MongoClient.Disconnect , MongoDB is closed!")// 刷新日志缓冲区// Sync调用底层核心的Sync方法,刷新所有缓冲的日志条目。应用程序应该注意在退出之前调用Sync。logger.Sync()
}

route.go

package routeimport ("context""fmt""github.com/gin-gonic/gin""log""net/http""os""os/signal""time""vsavefile/internal/constant""vsavefile/internal/logger""vsavefile/internal/middleware""vsavefile/internal/route/controller"
)var Router = &router{}type router struct {engine *gin.Engine
}func (p *router) InitRoute() {p.engine = gin.Default()//上传文件时只限制程序可以使用多少内存,而不限制上传文件的大小,即使文件大小比这更大,也会写入临时文件p.engine.MaxMultipartMemory = 8 << 20 // 8 MiB//p.AddRoute()
}func (p *router) AddRoute() {//使用SetTrustedProxies 来信任此ip代理//router.SetTrustedProxies([]string{"192.168.1.2"})//如下是gin框架的要求, { 左括号必须重起一行,TODO 封装vsavefileGroup := p.engine.Group("/cloudsave")vsavefileGroup.POST("/upload", middleware.CheckToken, controller.Upload)vsavefileGroup.GET("/download", middleware.CheckToken, controller.Download)//{//  vsavefileGroup.POST("/upload", func(ctx *gin.Context) {//     //upload(ctx)// })//    vsavefileGroup.GET("/download", func(ctx *gin.Context) {//        //download(ctx)//   })//}
}func (p *router) Run(host, port string) {//TODO 优化zap log日志,修改成自定义的zap 的logger//err := p.engine.Run(host + ":" + port)//if err != nil {// //路由启动失败,终止程序//  panic(err)//}//下面所有代码(go1.8+)是为了优雅处理重启等动作srv := &http.Server{Addr:         host + ":" + port,Handler:      p.engine,ReadTimeout:  constant.ReadTimeoutT * time.Second,WriteTimeout: constant.WriteTimeoutT * time.Second,}go func() {// 监听请求if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("listen: %s\n", err)}}()// 优雅Shutdown(或重启)服务// 有一点需要注意的是:这里设置的重启/关闭条件是 收到kill信号,即程序意外终止// 假如,我在这里用一个go携程,让它在2秒之后,panic程序,并且不进行recover恢复程序,那么panic会给进程发送一个os.Exit(2)(源码中可以看),// 这个是会被认为 程序主动退出 ,而非被kill 也就是进程不会收到 kill信号,也就无法重启/关闭了 (虽然panic让程序崩溃了)// 当注释掉下方go程中的defer的时候,程序直接panic,而不重启,反之则可以重启//go func() {//  defer Recover()//// time.Sleep(2 * time.Second)//   panic("dangerous")//}()quit := make(chan os.Signal)signal.Notify(quit, os.Interrupt) // syscall.SIGKILL<-quitlogger.Info("Shutdown Server...")// 10秒后,关闭context-即服务器于10秒后重启,或关闭(gin中的Context,也就是说客户端发送的一次请求,如果在10秒内没处理完,那么这个ctx将会失效,即无法在服务器重启前,成功处理客户端的请求)//TODO 设置一个context失效时间ctx, cancel := context.WithTimeout(context.Background(), constant.ServerRestart*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil {logger.Fatalf("Server Shutdown: ", err)}select {case <-ctx.Done():}logger.Info("Server exiting...")
}func Recover() {if p := recover(); p != nil {fmt.Println("p = ", p)}
}

controller.go

package controllerimport ("fmt""github.com/gin-gonic/gin""vsavefile/internal/logic"
)var ctxChanMap map[*gin.Context]chan intfunc init() {ctxChanMap = make(map[*gin.Context]chan int)
}func Upload(ctx *gin.Context) {fmt.Println("Uploading...")// 测试服务器shutdown 和 restart的功能 - 测试时,设置服务器context生命时间为10秒time.Sleep(5 * time.Second)// 用于测试,此次请求将在5秒之后,才将结果返回给客户端,在这5秒内如果服务器关闭,也能确保结果正确返回// 优雅的退出,在route.go中的Run中设置了
}func Download(ctx *gin.Context) {ctx.Writer.Write([]byte("download..."))
}

如下,捕捉到了ctrl+c对应的信号之后,执行了退出操作,重启服务的时候也可以执行退出操作。

go 服务器如何优雅的退出、重启相关推荐

  1. 【知识必备】如何优雅的退出应用和处理崩溃异常并重启

    [知识必备]如何优雅的退出应用和处理崩溃异常并重启 参考文章: (1)[知识必备]如何优雅的退出应用和处理崩溃异常并重启 (2)https://www.cnblogs.com/liushilin/p/ ...

  2. 如何优雅的退出/关闭/重启gunicorn进程

    在工作中,会发现gunicorn启动的web服务,无论怎么使用kill -9 进程号都是无法杀死gunicorn,经过我一番百度和谷歌,发现想要删除gunicorn进程其实很简单. 1. 寻找mast ...

  3. 如何优雅地退出python程序

    如何优雅地退出python程序 一个单模的Python程序,启动之后要能够优雅地关闭.即当用户按Ctrl+C或者kill pid的时候,程序都能从容关闭.实现起来非常简单. [python] view ...

  4. libevent的线程优雅的退出方式

    关键代码时: 主要是主线程必须等待子线程退出才可以 测试代码 #include "RecvData.h"static struct event_base *base; static ...

  5. 修改服务器ip 需要重启,linux服务器设置ip后需要重启吗

    linux服务器设置ip后需要重启吗 内容精选 换一换 切换操作系统是为您的弹性云服务器重新切换一个系统盘.切换完成后弹性云服务器的系统盘ID会发生改变,并删除原有系统盘.如果弹性云服务器当前使用的操 ...

  6. 修改服务器端口后防火墙要设置吗,服务器设置完防火墙需要重启吗

    服务器设置完防火墙需要重启吗 内容精选 换一换 代码迁移工具进行代码迁移时,需要调用Linux下的rpm.deb等命令才能完成扫描和迁移相关任务,这些命令和逻辑必须在后端Linux运行.IDE插件只支 ...

  7. 服务器凌晨莫名奇妙自动重启解决方案

    服务器凌晨莫名奇妙自动重启解决方案 转载请注明出处:http://blog.csdn.net/aaa123524457/article/details/47609701 问题描述: 这两天早上到公司, ...

  8. lol服务器维护退出来,【lol服务器连接异常.即将退出,清重新进入游戏】_服务器 关键词_全球新能源网...

    [专家解说]: 您可以参照下列提示操作下,希望我的回答对您有所帮助! ~ [原因分析]: 1. 虚拟内存不足 2. 游戏启动提示缺少某些D3D文件的报错 3. QQ安装后无法启动及部分游戏启动报错 4 ...

  9. 手机为什么显示服务器异常即将退出游戏,LOL服务器连接异常即将退出怎么回事...

    玩英雄联盟游戏过程中提示"服务器连接异常即将退出",但是检查网络是没有问题的,那么有可能就是防火墙问题了,那么遇到这个问题该怎么办?不知道的朋友赶紧看看以下解决方法吧! Windo ...

最新文章

  1. idea(3)-jetty配置
  2. SAP MM 执行事务代码MRRL报错-No message was found for partner 100065 company code 0001-
  3. php web目录结构,目录结构
  4. layui的富文本编辑器如何上传图片,以及后期处理
  5. android 队列执行动画,Android 重学系列 渲染图层-图元缓冲队列初始化
  6. 罗切斯特大学排名计算机排名,罗切斯特大学排名
  7. oracle的逻辑结构包括,oracle逻辑结构分析
  8. 语言软件生成outsid_常用的C语言开发工具有哪些
  9. 检测鼠标是否双击_Rhino细分建模分享 Part3 鼠标简易结构设计
  10. 20121214收藏
  11. 数理在线计算机,原码/反码/补码在线计算器
  12. 在 Half-Life MOD 中创建自己的武器及弹药
  13. macbook linux 双系统,mac安装linux双系统的吐槽
  14. mysql为什么要用b+树
  15. 算法基础课【合集1】
  16. E - EXCEL排序
  17. grep和egrep的区别
  18. 转换英文货币大小写的代码
  19. Kali渗透测试:使用browser_autopwn2模块进行渗透攻击
  20. 如何卸载Model Sim

热门文章

  1. 【报告分享】2021青年男女健康报告-腾讯新闻x冈本(附下载)
  2. 经典Oracle图书
  3. 阿里云混合云管理平台多Region架构
  4. Matlab 机械形貌仿真-铣刀
  5. 小试牛刀之《魔兽世界》战士怒气监控插件CombatTracker v1.0
  6. Codeforces Round #843 (Div. 2) 口胡日记(青大蒟蒻日常掉大分)
  7. ai绘画工具Stable Diffusion设置中文界面(保姆级)
  8. directshow捕获摄像头数据
  9. Flink算子distinct
  10. pandas中的 loc的使用(pandas.DataFrame.loc)