【Gin-API系列】守护进程和平滑重启(八)
生产环境的API服务我们都会部署在Linux服务器上,为了不受终端状态的影响,启动服务的时候会让服务在后台运行。那么如何让服务在后台运行呢,目前有2种常见的方法。
1、nohub 运行
表示忽略SIGHUP
(挂断)信号,终端退出的时候所发起的挂断信号会被忽略。nohup
一般会结合&
参数运行程序,&
表示将程序设置为后台运行的程序。两者结合就变成了启动一个不受终端状态影响的后台服务。
nohup gin-ips >> gin-api.out 2>&1 &
2、守护进程
- 理解守护进程
守护进程是一个在后台运行并且不受任何终端控制的进程。使用守护进程的好处是该进程永远以后台方式启动,生命周期一般都是和系统的启动关闭状态保持一致。
- 守护进程和后台进程的区别
守护进程和
nohup
+&
启动的后台进程区别并不大,都是脱离终端的。但在进程组、文件掩码、工作目录、标准/错误输出输入等会有不同。
对于Gin-IPs来说,用守护进程可以一键后台启动,并将日志输出到指定文件,非常方便。
- 创建守护进程
1、创建子进程,停止父进程
2、在子进程中创建新会话
3、改变工作目录
4、重设文件创建掩码
5、重定向文件描述符
Gin-API 创建守护进程
- 实现函数
/*
Linux Mac 下运行
守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
本程序只fork一次子进程,fork第二次主要目的是防止进程再次打开一个控制终端(不是必要的)。因为打开一个控制终端的前台条件是该进程必须是会话组长,再fork一次,子进程ID != sid(sid是进程父进程的sid),所以也无法打开新的控制终端
*/
package daemon
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
//var daemon = flag.Bool("d", false, "run app as a daemon process with -d=true")
func InitProcess() {
if syscall.Getppid() == 1 {
if err := os.Chdir("./"); err != nil {
panic(err)
}
syscall.Umask(0) // TODO TEST
return
}
fmt.Println("go daemon!!!")
fp, err := os.OpenFile("daemon.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer func() {
_ = fp.Close()
}()
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.Stdout = fp
cmd.Stderr = fp
cmd.Stdin = nil
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // TODO TEST
if err := cmd.Start(); err != nil {
panic(err)
}
_, _ = fp.WriteString(fmt.Sprintf(
"[PID] %d Start At %s\n", cmd.Process.Pid, time.Now().Format("2006-01-02 15:04:05")))
os.Exit(0)
}
- 初始化
func main() {
daemon.InitProcess()
// ...
}
Gin-API 平滑重启
创建守护进程之后,我们的程序已经能够在后台正常跑通了,但这样还有个问题,那就是在重启服务时候怎么保证服务不中断?
例如Nginx这种7*24小时接收请求的服务,在程序升级、配置文件更新、或者插件加载的时候就需要重启,为保证重启过程不中断服务,我们会使用平滑重启
- 平滑重启原理
gin-api
服务作为协程启动,做相应的处理并返回数据给客户端;主进程负责监听信号,根据信号进行关闭、重启操作
- 平滑重启步骤
1、主进程(原进程中的主进程)启动协程处理http请求,主进程开始监听终端信号
2、使用kill -USR2 $pid
发起停止主进程的动作
3、主进程接收到信号量12 (SIGUSR2)
后, 启动新的子进程,子进程接管父进程的标准输出、错误输出和socket
描述符
4、子进程同样启动协程处理请求,子进程中的主进程继续监听终端信号
5、父进程中的主进程发起关闭协程的动作,该协程处理完所有请求后自动关闭(平滑关闭)
6、父进程中的主进程退出
- 使用 http.Server
由于
gin
库函数缺少上下文管理功能,所以我们需要使用http.Server
来包裹gin
服务,支持对服务的平滑关闭功能
- 实现方式
func (server *Server) Listen(graceful bool) error {
addr := fmt.Sprintf("%s:%d", server.Host, server.Port)
httpServer := &http.Server{
Addr: addr,
Handler: server.Router,
}
// 判断是否为 reload
var err error
if graceful {
server.Logger.Info("listening on the existing file descriptor 3")
//子进程的 0 1 2 是预留给 标准输入 标准输出 错误输出
//因此传递的socket 描述符应该放在子进程的 3
f := os.NewFile(3, "")
// 获取 上个服务程序的 socket 的描述符
server.Listener, err = net.FileListener(f)
} else {
server.Logger.Info("listening on a new file descriptor")
server.Listener, err = net.Listen("tcp", httpServer.Addr)
server.Logger.Infof("Actual pid is %d\n", syscall.Getpid())
}
if err != nil {
server.Logger.Error(err)
return err
}
go func() {
// 开启服务
if err := httpServer.Serve(server.Listener); err != nil && err != http.ErrServerClosed {
err = errors.New(fmt.Sprintf("listen error:%v\n", err))
server.Logger.Fatal(err) // 报错退出
}
}()
return server.HandlerSignal(httpServer)
}
func (server *Server) HandlerSignal(httpServer *http.Server) error {
sign := make(chan os.Signal)
signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
for {
// 接收信号量
sig := <-sign
server.Logger.Infof("Signal receive: %v\n", sig)
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
// 关闭服务
server.Logger.Info("Shutdown Api Server")
signal.Stop(sign) // 停止通道
if err := httpServer.Shutdown(ctx); err != nil {
err = errors.New(fmt.Sprintf("Shutdown Api Server Error: %s", err))
return err
}
return nil
case syscall.SIGUSR2:
// 重启服务
server.Logger.Info("Reload Api Server")
// 先启动新服务
if err := server.Reload(); err != nil {
server.Logger.Errorf("Reload Api Server Error: %s", err)
continue
}
// 关闭旧服务
if err := httpServer.Shutdown(ctx); err != nil {
err = errors.New(fmt.Sprintf("Shutdown Api Server Error: %s", err))
return err
}
if err := destroyMgoPool(); err != nil {
return err
}
server.Logger.Info("Reload Api Server Successful")
return nil
}
}
}
func (server *Server) Reload() error {
tl, ok := server.Listener.(*net.TCPListener)
if !ok {
return errors.New("listener is not tcp listener")
}
f, err := tl.File()
if err != nil {
return err
}
// 命令行启动新程序
args := []string{"-graceful"}
cmd := exec.Command(os.Args[0], args...)
cmd.Stdout = os.Stdout // 1
cmd.Stderr = os.Stderr // 2
cmd.ExtraFiles = []*os.File{f} // 3
if err := cmd.Start(); err != nil {
return err
}
server.Logger.Infof("Forked New Pid %v: \n", cmd.Process.Pid)
return nil
}
守护进程和平滑重启的功能在生产环境上经常被使用,但要注意的是只能运行在Unix环境下。使用了这2个功能之后,程序在部署架构的时候就能发挥高可用的功能。
下一章,我们将介绍如何在生产环境部署服务。
Github 代码
请访问 Gin-IPs 或者搜索 Gin-IPs
【Gin-API系列】守护进程和平滑重启(八)的更多相关文章
- Golang学习--平滑重启
在上一篇博客介绍TOML配置的时候,讲到了通过信号通知重载配置.我们在这一篇中介绍下如何的平滑重启server. 与重载配置相同的是我们也需要通过信号来通知server重启,但关键在于平滑重启,如果只 ...
- [Linux] PHP程序员玩转Linux系列-使用supervisor实现守护进程
1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...
- 【学习笔记】启动Nginx、查看nginx进程、查看nginx服务主进程的方式、Nginx服务可接受的信号、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级
1.启动nginx的方式: cd /usr/local/nginx ls ./nginx -c nginx.conf 2.查看nginx的进程方式: [root@localhost nginx] ...
- flask使用debug模式时,存在错误时,会占用设备内存直至服务重启才释放;debug模式会开启一个守护进程(daemon process)
函数调用顺序flask的app.py的run-->werkzeug的serving.py的run_simple-->调用werkzeug的debug的__init__.py里的类Debug ...
- 启动Nginx、查看nginx进程、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级
1.启动nginx的方式: cd /usr/local/nginx ls
- 写一个Windows上的守护进程(8)获取进程路径
写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路 ...
- linux守护进程解读
Linux系统守护进程详解 不要关闭下面这几个服务: acpid, haldaemon, messagebus, klogd, network, syslogd 1. NetworkManag ...
- Linux学习之守护进程详解
Linux系统守护进程详解 ---转自:http://yuanbin.blog ...
- 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)
本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...
随机推荐
- 有哪些开源的 Python 库让你相见恨晚?
Arrow 我们知道 Python 已经内置了好几个处理时间相关的库,但是对于时间以及时区间的转换并不清晰,操作起来略繁琐,而 Arrow 可以弥补这个问题,它提供了更友好的方法,方便我们对时间,日期 ...
- spring boot 中使用spring security阶段小结
1 项目结构图 2 AnyUserDetailsService package com.fengyntec.config; import com.fengyntec.entity.UserEntity ...
- Visual Studio安装
2017 安装的时候,一直显示,安装成功但是有告警. 解决方法: 将visual studio 2017 installer进行卸载,然后安装hw的ios 不能确保下次也可以成功
- Vue中diff算法的理解
Vue中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结 ...
- Django-model模型中Field属性类别及选项
参考:[Django官方文档] Django所使用模型中一些属性类别及选项(Field and Options) 1. Models Field 各种类型分别对应数据库中的各种类型,这是Django对 ...
- golang 数据类型/基础语法
常量 变量 复合类型 结构体 数组 基础类型 整型 浮点型 复数 bool 值 字符型 字符串 错误(稍微有异议) 引用类型 切片 指针 字典 管道 函数 接口 其他语法结构 包 流程控制 运算符 注 ...
- jieba分词的几种形式
1.精确模式:试图将句子最精确地分开,适合文本分析 seg_list = jieba.cut(test_text, cut_all=False) seg_list = " ".jo ...
- 单元测试报错:Mybatis中数据库语句错误
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.Persiste ...
- 初识ABP vNext(3):vue对接ABP基本思路
目录 前言 开始 登录 权限 本地化 创建项目 ABP vue-element-admin 最后 前言 上一篇介绍了ABP的启动模板以及AbpHelper工具的基本使用,这一篇将进入项目实战部分.因为 ...
- 群晖系统设置链路聚合并配置静态IP的教程【江东网 JDX86.COM】
1.进入控制面板 > 网络 > 网络接口.请单击创建 > 创建 Bond 2.进入聚合配置向导,选择你想要的模式,这里有几种模式意思分别为: 自适应负载平衡: 此模式优化了 Syno ...