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