ginapi服务器性能,【Gin-API系列】守护进程和平滑重启(八)

生产环境的API服务我们都会部署在Linux服务器上,为了不受终端状态的影响,启动服务的时候会让服务在后台运行。那么如何让服务在后台运行呢,目前有2种常见的方法。

1、nohub 运行

表示忽略SIGHUP(挂断)信号,终端退出的时候所发起的挂断信号会被忽略。nohup一般会结合&参数运行程序,&表示将程序设置为后台运行的程序。两者结合就变成了启动一个不受终端状态影响的后台服务。nohup gin-ips >> gin-api.out 2>&1 &

2、守护进程理解守护进程守护进程和后台进程的区别创建守护进程

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 平滑重启

创建守护进程之后,我们的程序已经能够在后台正常跑通了,但这样还有个问题,那就是在重启服务时候怎么保证服务不中断?平滑重启原理平滑重启步骤使用 http.Server实现方式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 :=

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-admin-api是一个基于Gin框架开发的后台管理系统的API接口。Gin框架是一个轻量级的、高性能的Go语言框架,具有路由和中间件的功能,适合用于构建Web应用程序。 gin-admin-api提供了一套完善的API接口,用于实现后台管理系统的各种功能,例如用户管理、角色管理、权限管理、菜单管理、日志管理等。通过这些接口,可以方便地进行用户的注册、登录和认证,管理用户的角色和权限,管理系统的菜单和日志信息。 gin-admin-api的优点之一是高性能。由于采用了Gin框架,它具有快速的路由匹配和中间件处理的能力,能够处理大量的请求,并在高并发的情况下保持稳定性和可靠性。 另一个优点是易于扩展和定制。gin-admin-api使用了模块化的设计,各个功能模块之间松耦合,可以根据实际需求进行灵活的扩展和定制。例如,可以根据业务需求添加新的功能模块,或者修改和优化已有的模块。 此外,gin-admin-api还提供了友好的文档和示例代码,方便开发者理解和使用。它的源代码也是开源的,可以在GitHub上找到,这样可以方便地进行二次开发和定制,满足特定的业务需求。 总之,gin-admin-api是一个功能丰富、高性能、易扩展的后台管理系统API接口,大大简化了后台管理系统的开发工作,帮助开发者快速构建稳定、可靠的后台管理系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值