用golang开发的项目越来越多了,他们都跑在服务器上。但是他们都是在shell中运行的,如果关闭了终端,它就自动停止了。这显然不符合我们的需求,服务中断了还怎么服务用户啊。现在市面上流行的有几种解决方案,最简单的是使用nohup /data/wwwroot/build_app& 来实现将进程抛到后台去。也可以使用pm2来启动管理进程的。使用supervise的。也有使用daemon来实现的。下面我们就尝试使用daemon的方式来实现一个守护进程。

golang的daemon守护进程的代码实现

golang中启用daemon的方法很简单,已经有别人造好的轮子了,我们就不再造一遍,直接引用即可。使用的是syscall来处理进程。网上的实例使用的是http.Server服务,由于我使用的是iris框架,所以我重新实现了一个针对iris框架可用的daemon守护进程实现方式。它支持启动、关闭、重启等命令,模仿这nginx的命令操作来实现的。

核心代码

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"syscall"
	"video"

	"github.com/medivh-jay/daemon"
	"github.com/spf13/cobra"
)

// HTTPServer http 服务器示例
type HTTPServer struct {
	http *video.Bootstrap
	cmd  *cobra.Command
}

// PidSavePath pid保存路径
func (httpServer *HTTPServer) PidSavePath() string {
	return "./"
}

// Name pid文件名
func (httpServer *HTTPServer) Name() string {
	return "http"
}

// SetCommand 从 daemon 获得 cobra.Command 对象
func (httpServer *HTTPServer) SetCommand(cmd *cobra.Command) {
	// 在这里添加参数时他的参数不是对应服务的 start stop restart 命令的, 比如这个示例服务
	// 他对应的是示例服务命令, s所以这里添加的自定义 flag 应该在 start 之前传入
	cmd.PersistentFlags().StringP("test", "t", "yes", "")
	httpServer.cmd = cmd
}

// Start 启动web服务
func (httpServer *HTTPServer) Start() {
	fmt.Println(httpServer.cmd.Flags().GetString("test"))
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println("hello world")
		_, _ = writer.Write([]byte("hello world"))
	})
	httpServer.http = video.New(8088, "debug")
	httpServer.http.Serve()
}

// Stop 关闭web服务
func (httpServer *HTTPServer) Stop() error {
	fmt.Println("准备关闭服务器")
	err := httpServer.http.Shutdown()
	fmt.Println("服务器已经关闭")
	return err
}

// Restart 重启web服务前关闭http服务
func (httpServer *HTTPServer) Restart() error {
	fmt.Println("服务器关闭中")
	err := httpServer.Stop()
	return err
}

func main() {
	// 自定义输出文件
	out, _ := os.OpenFile("./http.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
	err, _ := os.OpenFile("./http_err.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)

	// 初始化一个新的运行程序
	proc := daemon.NewProcess(new(HTTPServer)).SetPipeline(nil, out, err)
	proc.On(syscall.SIGTERM, func() {
		fmt.Println("a custom signal")
	})
	// 示例,多级命令服务
	// 不要共享一个 worker 对象指针
	daemon.GetCommand().AddWorker(proc).AddWorker(proc)
	// 示例,主服务
	daemon.Register(proc)

	// 运行
	if rs := daemon.Run(); rs != nil {
		log.Fatalln(rs)
	}
}

bootstrap里面是iris的逻辑

package video

import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/middleware/recover"
)

type Bootstrap struct {
	Application *iris.Application
	Port        int
	LoggerLevel string
}

func New(port int, loggerLevel string) *Bootstrap {
	var bootstrap Bootstrap
	bootstrap.Application = iris.New()
	bootstrap.Port = port
	bootstrap.LoggerLevel = loggerLevel

	return &bootstrap
}

func (bootstrap *Bootstrap) loadGlobalMiddleware() {
	bootstrap.Application.Use(recover.New())
}

func (bootstrap *Bootstrap) LoadRoutes() {
	Register(bootstrap.Application)
}

func (bootstrap *Bootstrap) Serve() {
	bootstrap.Application.Logger().SetLevel(bootstrap.LoggerLevel)
	bootstrap.loadGlobalMiddleware()
	bootstrap.LoadRoutes()

	bootstrap.Application.Run(
		iris.Addr(fmt.Sprintf(":%d", bootstrap.Port)),
		iris.WithoutServerError(iris.ErrServerClosed),
		iris.WithoutBodyConsumptionOnUnmarshal,
	)
}

func (bootstrap *Bootstrap) Shutdown() error {
	bootstrap.Shutdown()

	return nil
}

控制器代码

package video

import (
	"github.com/kataras/iris/v12"
	"os"
	"time"
)

func Index(ctx iris.Context) {
  ctx.WriteString("hello")
}

func ParseVideo(ctx iris.Context) {
	v, err := os.Open("vv.mp4")
	if err != nil {
		ctx.WriteString(err.Error())
		return
	}
	defer v.Close()
	ctx.ServeContent(v, "vv.mp4", time.Now(), true)
}

路由器代码

package video

import "github.com/kataras/iris/v12"

func Register(app *iris.Application) {
	app.Use(Cors)
	app.Use(ParseToken)

  app.Get("/", Index)
	app.Get("/play", ParseVideo)
}

中间件代码,用来处理cors

package video

import (
	"github.com/kataras/iris/v12"
)

func Cors(ctx iris.Context) {
	origin := ctx.GetHeader("Origin")
	if origin == "" {
		origin = ctx.GetHeader("Referer")
	}
	ctx.Header("Access-Control-Allow-Origin", origin)
	ctx.Header("Access-Control-Allow-Credentials", "true")
	ctx.Header("Access-Control-Expose-Headers", "Content-Disposition")
	if ctx.Request().Method == "OPTIONS" {
		ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")
		ctx.Header("Access-Control-Allow-Headers", "Content-Type, Api, Accept, Authorization, Admin")
		ctx.StatusCode(204)
		return
	}
	ctx.Next()
}

func ParseToken(ctx iris.Context) {

	ctx.Next()
}