热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Golang】程序如何优雅的退出?

1.背景项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?为什么需要优雅的退出?你的http服务,监听端口没有关闭,客户的请求发过来了,

1. 背景

  项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?

 

为什么需要优雅的退出?

  • 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据。
  • 你的协程 worker 的一个任务运行了一半,程序退出了,结果不符合预期。

 

如下我们以 http 服务,gRPC 服务,单独的 woker 协程为例子,一步步说明平滑关闭的写法。

 

2. 常见的几种平滑关闭

为了解决退出可能出现的潜在问题,平滑关闭一般做如下一些事情

  • 关闭对外的监听端口,拒绝新的连接
  • 关闭异步运行的协程
  • 关闭依赖的资源
  • 等待如上资源关闭
  • 然后平滑关闭

2.1 http server 平滑关闭

原来的写法

// startHttpServer start http server
func startHttpServer() {
	mux := http.NewServeMux()
	// mux.Handle("/metrics", promhttp.Handler())
	if err := http.ListenAndServe(":1608", mux); err != nil {
		log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
	}
}

  

带平滑关闭的写法

// startHttpServer start http server
func startHttpServer() {
	mux := http.NewServeMux()
	// mux.Handle("/metrics", promhttp.Handler())
	srv := &http.Server{
		Addr:    ":1608",
		Handler: mux,
	}
	// 注册平滑关闭,退出时会调用 srv.Shutdown(ctx)
	quit.GetQuitEvent().RegisterQuitCloser(srv)
	if err := srv.ListenAndServe(); err != nil {
		log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
	}
}

把平滑关闭注册到http.Server的关闭函数中

// startHttpServer start http server
func startHttpServer() {
	mux := http.NewServeMux()
	// mux.Handle("/metrics", promhttp.Handler())
	srv := &http.Server{
		Addr:    ":1608",
		Handler: mux,
	}
	// 把平滑退出注册到http.Server中
	srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop)
	if err := srv.ListenAndServe(); err != nil {
		log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
	}
}

  

2.2 gRPC server 平滑关闭

原来的写法

// startGrpcServer start grpc server
func startGrpcServer() {
	listen, err := net.Listen("tcp", "0.0.0.0:9999")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
		return
	}
	grpcServer := grpc.NewServer()
	// helloBoot.GrpcRegister(grpcServer)
	go grpcServer.Serve(listen)
	defer grpcServer.GracefulStop()
	// ...
}

带平滑关闭的写法 

// startGrpcServer start grpc server
func startGrpcServer() {
	listen, err := net.Listen("tcp", "0.0.0.0:9999")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
		return
	}
	grpcServer := grpc.NewServer()
	// helloBoot.GrpcRegister(grpcServer)
	go grpcServer.Serve(listen)
	// 把 grpc 的GracefulStop注册到退出事件中
	quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
	quit.WaitSignal()
}

  

2.3 worker 协程平滑关闭

单独的协程启停,可以通过计数的方式注册到退出事件处理器中。

  • 启动协程 增加计数
    •  quit.GetQuitEvent().AddGoroutine()
  • 停止协程 减计数 
    •  quit.GetQuitEvent().DoneGoroutine()
  • 常驻后台运行的协程退出的条件改成退出事件是否结束的条件 
    • !quit.GetQuitEvent().HasFired()
  • 常驻后台运行的协程若通过 select 处理 chan,同时增加退出事件的chan
    •  case <-quit.GetQuitEvent().Done()
// myWorker my worker
type myWorker struct {
}

// RunWorkerWithChan run Goroutine worker
func (m *myWorker) RunWorkerWithChan() {
	// 启动一个Goroutine时,增加Goroutine数
	quit.GetQuitEvent().AddGoroutine()
	defer func() {
		// 一个Goroutine退出时,减少Goroutine数
		quit.GetQuitEvent().DoneGoroutine()
	}()
	// 退出时,此次退出
	for !quit.GetQuitEvent().HasFired() {
		select {
		// 退出时,收到退出信号
		case <-quit.GetQuitEvent().Done():
			break
			//case msg := <- m.YouChan:
			// handle msg
		}
	}
}

// RunWorker run Goroutine worker
func (m *myWorker) RunWorker() {
	// 启动一个Goroutine时,增加Goroutine数
	quit.GetQuitEvent().AddGoroutine()
	defer func() {
		// 一个Goroutine退出时,减少Goroutine数
		quit.GetQuitEvent().DoneGoroutine()
	}()

	// 退出时,此次退出
	for !quit.GetQuitEvent().HasFired() {
		// ...
	}
}

  

2.4 实现 io.Closer 接口的自定义服务平滑关闭

实现 io.Closer 接口的结构体,增加到退出事件处理器中 

// startMyService start my service
func startMyService() {
	srv := NewMyService()
	// 注册平滑关闭,退出时会调用 srv.Close()
	quit.GetQuitEvent().RegisterCloser(srv)
	srv.Run()
}

// myService my service
type myService struct {
	isStop bool
}

// NewMyService new
func NewMyService() *myService {
	return &myService{}
}

// Close my service
func (m *myService) Close() error {
	m.isStop = true
	return nil
}

// Run my service
func (m *myService) Run() {
	for !m.isStop {
		// ....
	}
}

  

2.5 集成其他框架怎么做

退出信号处理由某一框架接管,寻找框架如何注册退出函数,优秀的框架一般都会实现安全实现退出的机制。

如下将退出事件注册到某一框架的平滑关闭函数中

func startMyServer() {
	// ...
	// xxx框架退出函数注册退出事件
	xxx.RegisterQuitter(func() {
		quit.GetQuitEvent().GracefulStop()
	})
}

 

参考:

https://github.com/mygityf/go-library/blob/main/quit/quit.go

 

完。

祝玩的开心~


推荐阅读
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Istio是一个用来连接、管理和保护微服务的开放平台。Istio提供一种简单的方式来为已部署的服务建 ... [详细]
  • 微服务之总体架构篇
    一、单体架构存在的问题缺点:1、难以维护:当单体应用业务不断迭代后代码量非常臃肿,模整个项目非常复杂,每次更改代码都可能带来新的bug;2、部署项目麻烦:庞大之后项目部署效率 ... [详细]
  • 在这分布式系统架构盛行的时代,很多互联网大佬公司开源出自己的分布式RPC系统框架,例如:阿里的dubbo,谷歌的gRPC,apache的Thrift。而在我们公司一直都在推荐使用d ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 基于.NET Core框架nacos的简单应用
    什么是Nacos?服务(Service)是Nacos世界的一等公民。Nacos支持 ... [详细]
author-avatar
香音的茶
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有