生产环境的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系列】守护进程和平滑重启(八)的更多相关文章

  1. Golang学习--平滑重启

    在上一篇博客介绍TOML配置的时候,讲到了通过信号通知重载配置.我们在这一篇中介绍下如何的平滑重启server. 与重载配置相同的是我们也需要通过信号来通知server重启,但关键在于平滑重启,如果只 ...

  2. [Linux] PHP程序员玩转Linux系列-使用supervisor实现守护进程

    1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...

  3. 【学习笔记】启动Nginx、查看nginx进程、查看nginx服务主进程的方式、Nginx服务可接受的信号、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级

     1.启动nginx的方式: cd /usr/local/nginx ls ./nginx -c nginx.conf 2.查看nginx的进程方式: [root@localhost nginx] ...

  4. flask使用debug模式时,存在错误时,会占用设备内存直至服务重启才释放;debug模式会开启一个守护进程(daemon process)

    函数调用顺序flask的app.py的run-->werkzeug的serving.py的run_simple-->调用werkzeug的debug的__init__.py里的类Debug ...

  5. 启动Nginx、查看nginx进程、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级

    1.启动nginx的方式: cd /usr/local/nginx ls

  6. 写一个Windows上的守护进程(8)获取进程路径

    写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路 ...

  7. linux守护进程解读

    Linux系统守护进程详解   不要关闭下面这几个服务: acpid, haldaemon, messagebus, klogd, network, syslogd   1. NetworkManag ...

  8. Linux学习之守护进程详解

    Linux系统守护进程详解                                                              ---转自:http://yuanbin.blog ...

  9. 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)

    本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...

随机推荐

  1. JavaFX桌面应用-MVC模式开发,“真香”

    使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...

  2. CSS3 新添选择器

    目录 属性选择器 结构伪类选择器 伪元素选择器 属性选择器 属性选择器可以元素特定属性来进行选择,这样就可以不借助于类选择器或id选择器 选择符 简述 E[att] 选择具有att属性的E元素 E[a ...

  3. PowerJob 的自实现高可用方案,妙妙妙!

    本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列. 碎碎念 高可用放到今天已经不是一个新颖的词汇了,怎 ...

  4. bluecms v1.6 代码审计

    0x01 使用seay源代码审计系统进行审计 扫描到了很多个可疑漏洞,不过工具都有一定的误报,下面我们就逐个进行验证 0x02 /ad_js.php SQL注入漏洞 查看源码,我们发现程序通过GET方 ...

  5. 漏洞重温之XSS(上)

    漏洞简介 跨站脚本攻击(XSS)是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览页面之时,嵌入web网页中的script代码会被执行,从而达到恶意攻击用户的目的. XSS漏洞通常是通过 ...

  6. 关于word2vec我有话要说

    写在前面的话: 总结一下使用word2vec一年来的一些经验,因为自己在做的时候,很难在网上搜到word2vec的经验介绍,所以归纳出来,希望对读者有用. 这里不介绍word2vec的原理,因为原理介 ...

  7. troubleshoot之:GC调优到底是什么

    目录 简介 那些GC的默认值 GC的选择 GC的最大线程个数 初始化heap size 最大的heap size 分层编译技术 我们到底要什么 最大暂停时间 吞吐率 简介 我们经常会听到甚至需要自己动 ...

  8. RPC 框架 Dubbo 从理解到使用(一)

    技术架构演变 单一应用架构 通俗地讲,"单体应用(monolith application)"就是将应用程序的所有功能都打包成一个独立的单元.当网站流量很小时,只需一个应用,将所有 ...

  9. 5. JsonFactory工厂而已,还蛮有料,这是我没想到的

    少年易学老难成,一寸光阴不可轻.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[ ...

  10. anaconda下载包时网络连接错误的解决方法(CondaHTTPError:HTTP 000 connection failed for url)

    继上一篇<在WSL上搭载python编程环境>之后,下载软件和创建新环境的过程非常艰辛,下载太慢,以至于常常中断. 不论用conda安装一些python的包,还是创新独立的编程环境时,出现 ...