Go 插件每日推荐之 go-daemon | Go 主题月
大家好,我是 @洛竹
本文首发于 洛竹的官方网站
本文同步于公众号『洛竹早茶馆』,转载请联系作者。
创作不易,养成习惯,素质三连!
在 Go 中编写系统守护进程的库。
现在只支持基于 UNIX 的操作系统(不支持 Windows)。但是这个库只在 Linux 和 OSX 上进行了测试,所以如果你有能力在其他平台上测试这个库,请给我反馈,谢谢。
请随时给我发送错误报告和修复。非常感谢所有的贡献者。
在一个多任务的电脑操作系统中,守护进程(英语:daemon,/ˈdiːmən/或/ˈdeɪmən/)是一种在后台执行的电脑程序。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母“d”结尾:例如,syslogd 就是指管理系统日志的守护进程。——维基百科
特性
- Go 协程安全的守护进程;
- 内置使用 pid 文件;
- 轻松处理系统信号;
- 守护进程的控制。
安装
go get github.com/sevlyar/go-daemon
你可以使用 gopkg.in:
go get gopkg.in/sevlyar/go-daemon.v0
如果你想在生产项目中使用该库,请使用 vendoring。因为在发布 v1.0 版本之前,我无法确保向后兼容。
示例
基础使用
package main
import (
"fmt"
"html"
"log"
"net/http"
"github.com/sevlyar/go-daemon"
)
// 要终止守护进程,使用:
// kill `cat sample.pid`。
func main() {
cntxt := &daemon.Context{
PidFileName: "sample.pid",
PidFilePerm: 0644,
LogFileName: "sample.log",
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
Args: []string{"[go-daemon sample]"},
}
d, err := cntxt.Reborn()
if err != nil {
log.Fatal("Unable to run: ", err)
}
if d != nil {
return
}
defer cntxt.Release()
log.Print("- - - - - - - - - - - - - - -")
log.Print("daemon started")
serveHTTP()
}
func serveHTTP() {
http.HandleFunc("/", httpHandler)
http.ListenAndServe("127.0.0.1:8080", nil)
}
func httpHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("request from %s: %s %q", r.RemoteAddr, r.Method, r.URL)
fmt.Fprintf(w, "go-daemon: %q", html.EscapeString(r.URL.Path))
}
信号处理
package main
import (
"flag"
"github.com/sevlyar/go-daemon"
"log"
"os"
"syscall"
"time"
)
var (
signal = flag.String("s", "", `Send signal to the daemon:
quit — graceful shutdown
stop — fast shutdown
reload — reloading the configuration file`)
)
func main() {
flag.Parse()
daemon.AddCommand(daemon.StringFlag(signal, "quit"), syscall.SIGQUIT, termHandler)
daemon.AddCommand(daemon.StringFlag(signal, "stop"), syscall.SIGTERM, termHandler)
daemon.AddCommand(daemon.StringFlag(signal, "reload"), syscall.SIGHUP, reloadHandler)
cntxt := &daemon.Context{
PidFileName: "sample.pid",
PidFilePerm: 0644,
LogFileName: "sample.log",
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
Args: []string{"[go-daemon sample]"},
}
if len(daemon.ActiveFlags()) > 0 {
d, err := cntxt.Search()
if err != nil {
log.Fatalf("Unable send signal to the daemon: %s", err.Error())
}
daemon.SendCommands(d)
return
}
d, err := cntxt.Reborn()
if err != nil {
log.Fatalln(err)
}
if d != nil {
return
}
defer cntxt.Release()
log.Println("- - - - - - - - - - - - - - -")
log.Println("daemon started")
go worker()
err = daemon.ServeSignals()
if err != nil {
log.Printf("Error: %s", err.Error())
}
log.Println("daemon terminated")
}
var (
stop = make(chan struct{})
done = make(chan struct{})
)
func worker() {
LOOP:
for {
time.Sleep(time.Second) // this is work to be done by worker.
select {
case <-stop:
break LOOP
default:
}
}
done <- struct{}{}
}
func termHandler(sig os.Signal) error {
log.Println("terminating...")
stop <- struct{}{}
if sig == syscall.SIGQUIT {
<-done
}
return daemon.ErrStop
}
func reloadHandler(sig os.Signal) error {
log.Println("configuration reloaded")
return nil
}
工作原理
我们不能在 Golang 的运行时使用 fork
系统调用,因为在这种情况下,子进程不会继承线程和协程。该库使用了一个简单的技巧:
它运行自己的副本,并带有一个标记--一个预定义的环境变量。该变量对进程的可用性意味着在子进程的副本中执行。因此,如果没有设置标记--库会执行父库的操作,并运行带有标记的自己的副本,如果设置了标记--库会执行子库的操作。
func main() {
Pre()
context := new(Context)
child, _ := context.Reborn()
if child != nil {
PostParent()
} else {
defer context.Release()
PostChild()
}
}
转载自:https://juejin.cn/post/6947271530023911461