生产环境的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. String 的两种实例化方式

    String 的两种实例化方式 隐式实例化:直接赋值 public class Demo { public static void main(String[] args) { String s = & ...

  2. IdentityServer4 (4) 静默刷新(Implicit)

    写在前面 1.源码(.Net Core 2.2) git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git 2.相关章节 2.1. ...

  3. 一篇文章快速搞懂什么是GitHub

    导读:什么是GitHub?Git与GitHub之间是什么关系?我们为什么需要版本控制系统?GitHub如何使用?本文将带你一探究竟. 本文字数:1710,阅读时长大约:13分钟 一.什么是版本控制 按 ...

  4. Java—构造方法及this/super/final/static关键字

    构造方法 构建创造时用的方法,即就是对象创建时要执行的方法. //构造方法的格式: 修饰符 构造方法名(参数列表) { } 构造方法的体现: 构造方法没有返回值类型.也不需要写返回值.因为它是为构建对 ...

  5. Linux内核之 进程管理

    正如上一篇我们提到过,进程是Linux系统中仅次于文件的基本抽象概念.正在运行的进程不仅仅是二进制代码,而是数据.资源.状态和虚拟的计算机组成.我们今天主要介绍进程的概念,组成,运行状态和生命周期等. ...

  6. SpringMVC大威天龙

    一 SpringMVC简介 SpringMVC是Spring提供的一个强大而灵活的Web框架 借助于注解 SpringMVC提供了几乎是POJO的开发模式 使得控制器的开发和测试更加简单 二 Spri ...

  7. Spring Boot 2.x基础教程:使用集中式缓存Redis

    之前我们介绍了两种进程内缓存的用法,包括Spring Boot默认使用的ConcurrentMap缓存以及缓存框架EhCache.虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进 ...

  8. Html5与CSS3(选择器)

    <!-- 作者:offline 时间:2018-03-21 描述:1.全选择器 *{属性1:属性值2:属性2:属性值2:...:} 2.元素(标签)选择器 标签名{属性1:属性值2:属性2:属性 ...

  9. 详解Apache Hudi如何配置各种类型分区

    1. 引入 Apache Hudi支持多种分区方式数据集,如多级分区.单分区.时间日期分区.无分区数据集等,用户可根据实际需求选择合适的分区方式,下面来详细了解Hudi如何配置何种类型分区. 2. 分 ...

  10. 致敬学长!J20航模遥控器开源项目计划【开局篇】 | 先做一个开机界面 | MATLAB图像二值化 | Img2Lcd图片取模 | OLED显示图片

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...