4.5.2 守护进程开发 #
守护进程(Daemon)是在后台运行的系统服务程序,它们通常在系统启动时启动,并持续运行直到系统关闭。守护进程不与任何终端关联,独立于用户会话运行。在 Go 语言中开发守护进程需要遵循 Unix/Linux 系统的特定规范和最佳实践。
守护进程基础概念 #
守护进程的特征 #
标准的 Unix 守护进程具有以下特征:
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
fmt.Println("=== 守护进程特征 ===")
// 1. 父进程 PID 为 1 (init 进程)
ppid := os.Getppid()
fmt.Printf("父进程 PID: %d\n", ppid)
// 2. 进程组 ID 和会话 ID
pid := os.Getpid()
pgid, _ := syscall.Getpgid(pid)
sid, _ := syscall.Getsid(pid)
fmt.Printf("进程 PID: %d\n", pid)
fmt.Printf("进程组 ID: %d\n", pgid)
fmt.Printf("会话 ID: %d\n", sid)
// 3. 工作目录
wd, _ := os.Getwd()
fmt.Printf("工作目录: %s\n", wd)
// 4. 文件权限掩码
fmt.Printf("当前 umask: %o\n", syscall.Umask(0))
syscall.Umask(0) // 重置 umask
// 5. 标准输入输出
fmt.Printf("标准输入: %v\n", os.Stdin.Name())
fmt.Printf("标准输出: %v\n", os.Stdout.Name())
fmt.Printf("标准错误: %v\n", os.Stderr.Name())
fmt.Println("\n守护进程应该具有:")
fmt.Println("- 父进程为 init (PID 1)")
fmt.Println("- 成为会话领导者")
fmt.Println("- 工作目录为根目录 /")
fmt.Println("- umask 设置为 0")
fmt.Println("- 关闭所有文件描述符")
fmt.Println("- 重定向标准输入输出到 /dev/null")
}
守护进程创建步骤 #
创建守护进程需要遵循标准的步骤:
package main
import (
"fmt"
"log"
"os"
"syscall"
)
// 守护进程创建步骤演示
func demonstrateDaemonSteps() {
fmt.Println("=== 守护进程创建步骤 ===")
fmt.Println("步骤 1: fork() 创建子进程,父进程退出")
fmt.Println(" - 确保子进程不是进程组领导者")
fmt.Println(" - 允许后续创建新会话")
fmt.Println("\n步骤 2: setsid() 创建新会话")
fmt.Println(" - 成为新会话的领导者")
fmt.Println(" - 成为新进程组的领导者")
fmt.Println(" - 脱离控制终端")
fmt.Println("\n步骤 3: 再次 fork() 并退出父进程")
fmt.Println(" - 确保进程不能重新获得控制终端")
fmt.Println(" - 只有会话领导者才能获得控制终端")
fmt.Println("\n步骤 4: 改变工作目录到根目录")
fmt.Println(" - 避免阻止文件系统卸载")
fmt.Println(" - 确保工作目录始终存在")
fmt.Println("\n步骤 5: 重置文件权限掩码")
fmt.Println(" - 设置 umask 为 0")
fmt.Println(" - 确保创建文件时权限正确")
fmt.Println("\n步骤 6: 关闭所有文件描述符")
fmt.Println(" - 关闭从父进程继承的文件描述符")
fmt.Println(" - 释放系统资源")
fmt.Println("\n步骤 7: 重定向标准输入输出")
fmt.Println(" - 重定向到 /dev/null")
fmt.Println(" - 避免意外的输入输出操作")
}
func main() {
demonstrateDaemonSteps()
}
基础守护进程实现 #
简单守护进程 #
让我们实现一个基础的守护进程:
package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
)
// Daemon 结构体
type Daemon struct {
name string
pidFile string
logFile string
}
// NewDaemon 创建新的守护进程实例
func NewDaemon(name, pidFile, logFile string) *Daemon {
return &Daemon{
name: name,
pidFile: pidFile,
logFile: logFile,
}
}
// Daemonize 将当前进程转换为守护进程
func (d *Daemon) Daemonize() error {
// 检查是否已经是守护进程
if os.Getppid() == 1 {
return nil // 已经是守护进程
}
// 第一次 fork
if err := d.forkAndExit(); err != nil {
return fmt.Errorf("第一次 fork 失败: %v", err)
}
// 创建新会话
if _, err := syscall.Setsid(); err != nil {
return fmt.Errorf("创建新会话失败: %v", err)
}
// 第二次 fork
if err := d.forkAndExit(); err != nil {
return fmt.Errorf("第二次 fork 失败: %v", err)
}
// 改变工作目录到根目录
if err := os.Chdir("/"); err != nil {
return fmt.Errorf("改变工作目录失败: %v", err)
}
// 重置文件权限掩码
syscall.Umask(0)
// 关闭文件描述符
if err := d.closeFileDescriptors(); err != nil {
return fmt.Errorf("关闭文件描述符失败: %v", err)
}
// 重定向标准输入输出
if err := d.redirectStdio(); err != nil {
return fmt.Errorf("重定向标准输入输出失败: %v", err)
}
return nil
}
// forkAndExit 执行 fork 并让父进程退出
func (d *Daemon) forkAndExit() error {
// 在 Go 中,我们使用 os.StartProcess 来模拟 fork
// 注意:这是一个简化的实现,实际生产环境中可能需要更复杂的处理
// 获取当前可执行文件路径
executable, err := os.Executable()
if err != nil {
return err
}
// 启动新进程
process, err := os.StartProcess(executable, os.Args, &os.ProcAttr{
Dir: "/",
Env: os.Environ(),
Files: []*os.File{
nil, // stdin
nil, // stdout
nil, // stderr
},
Sys: &syscall.SysProcAttr{
Setsid: true, // 创建新会话
},
})
if err != nil {
return err
}
// 父进程退出
process.Release()
os.Exit(0)
return nil
}
// closeFileDescriptors 关闭所有文件描述符
func (d *Daemon) closeFileDescriptors() error {
// 获取最大文件描述符数量
var rlimit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
return err
}
// 关闭所有文件描述符(除了 0, 1, 2)
for fd := 3; fd < int(rlimit.Cur); fd++ {
syscall.Close(fd)
}
return nil
}
// redirectStdio 重定向标准输入输出到 /dev/null
func (d *Daemon) redirectStdio() error {
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
return err
}
// 重定向标准输入
if err := syscall.Dup2(int(devNull.Fd()), int(os.Stdin.Fd())); err != nil {
return err
}
// 重定向标准输出
if err := syscall.Dup2(int(devNull.Fd()), int(os.Stdout.Fd())); err != nil {
return err
}
// 重定向标准错误
if err := syscall.Dup2(int(devNull.Fd()), int(os.Stderr.Fd())); err != nil {
return err
}
devNull.Close()
return nil
}
// WritePidFile 写入 PID 文件
func (d *Daemon) WritePidFile() error {
pidFile, err := os.Create(d.pidFile)
if err != nil {
return err
}
defer pidFile.Close()
_, err = fmt.Fprintf(pidFile, "%d\n", os.Getpid())
return err
}
// RemovePidFile 删除 PID 文件
func (d *Daemon) RemovePidFile() error {
return os.Remove(d.pidFile)
}
// Run 运行守护进程
func (d *Daemon) Run() error {
// 转换为守护进程
if err := d.Daemonize(); err != nil {
return err
}
// 写入 PID 文件
if err := d.WritePidFile(); err != nil {
return err
}
// 设置日志输出
if d.logFile != "" {
logFile, err := os.OpenFile(d.logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
log.SetOutput(logFile)
}
log.Printf("守护进程 %s 启动,PID: %d", d.name, os.Getpid())
// 设置信号处理
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
// 主循环
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case sig := <-sigChan:
log.Printf("接收到信号: %s", sig)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
log.Println("守护进程正在关闭...")
d.RemovePidFile()
return nil
case syscall.SIGHUP:
log.Println("重新加载配置...")
// 这里可以添加配置重载逻辑
}
case <-ticker.C:
log.Printf("守护进程 %s 正在运行...", d.name)
// 这里添加守护进程的主要工作逻辑
}
}
}
func main() {
daemon := NewDaemon("mydaemon", "/var/run/mydaemon.pid", "/var/log/mydaemon.log")
if err := daemon.Run(); err != nil {
log.Fatalf("守护进程运行失败: %v", err)
}
}
改进的守护进程实现 #
由于 Go 语言的特殊性,我们需要一个更实用的守护进程实现:
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
// DaemonConfig 守护进程配置
type DaemonConfig struct {
Name string
Description string
PidFile string
LogFile string
WorkDir string
User string
Group string
}
// GoDaemon Go 守护进程实现
type GoDaemon struct {
config *DaemonConfig
ctx context.Context
cancel context.CancelFunc
logger *log.Logger
}
// NewGoDaemon 创建新的 Go 守护进程
func NewGoDaemon(config *DaemonConfig) *GoDaemon {
ctx, cancel := context.WithCancel(context.Background())
return &GoDaemon{
config: config,
ctx: ctx,
cancel: cancel,
logger: log.New(os.Stdout, fmt.Sprintf("[%s] ", config.Name), log.LstdFlags),
}
}
// IsDaemon 检查是否在守护进程模式下运行
func (gd *GoDaemon) IsDaemon() bool {
return os.Getenv("DAEMON_MODE") == "1"
}
// Start 启动守护进程
func (gd *GoDaemon) Start() error {
if gd.IsDaemon() {
// 已经在守护进程模式下运行
return gd.runDaemon()
}
// 检查是否已经在运行
if gd.isRunning() {
return fmt.Errorf("守护进程已经在运行")
}
// 启动守护进程
return gd.startDaemon()
}
// startDaemon 启动守护进程
func (gd *GoDaemon) startDaemon() error {
// 获取当前可执行文件路径
executable, err := os.Executable()
if err != nil {
return err
}
// 准备命令
cmd := exec.Command(executable, os.Args[1:]...)
cmd.Env = append(os.Environ(), "DAEMON_MODE=1")
cmd.Dir = gd.config.WorkDir
// 设置进程属性
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true, // 创建新会话
}
// 重定向输入输出
if err := gd.setupStdio(cmd); err != nil {
return err
}
// 启动进程
if err := cmd.Start(); err != nil {
return err
}
// 写入 PID 文件
if err := gd.writePidFile(cmd.Process.Pid); err != nil {
cmd.Process.Kill()
return err
}
fmt.Printf("守护进程 %s 已启动,PID: %d\n", gd.config.Name, cmd.Process.Pid)
return nil
}
// runDaemon 运行守护进程主逻辑
func (gd *GoDaemon) runDaemon() error {
// 设置日志输出
if err := gd.setupLogging(); err != nil {
return err
}
gd.logger.Printf("守护进程 %s 启动,PID: %d", gd.config.Name, os.Getpid())
// 设置信号处理
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1)
// 启动工作协程
go gd.worker()
// 信号处理循环
for {
select {
case sig := <-sigChan:
gd.logger.Printf("接收到信号: %s", sig)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
gd.logger.Println("守护进程正在关闭...")
gd.cancel()
gd.cleanup()
return nil
case syscall.SIGHUP:
gd.logger.Println("重新加载配置...")
gd.reloadConfig()
case syscall.SIGUSR1:
gd.logger.Println("打印状态信息...")
gd.printStatus()
}
case <-gd.ctx.Done():
gd.logger.Println("守护进程上下文已取消")
return nil
}
}
}
// worker 工作协程
func (gd *GoDaemon) worker() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
gd.logger.Printf("守护进程 %s 正在运行...", gd.config.Name)
gd.doWork()
case <-gd.ctx.Done():
gd.logger.Println("工作协程收到关闭信号")
return
}
}
}
// doWork 执行实际工作
func (gd *GoDaemon) doWork() {
// 这里添加守护进程的主要工作逻辑
// 例如:处理队列、监控系统、数据同步等
gd.logger.Println("执行守护进程工作...")
// 模拟工作
time.Sleep(1 * time.Second)
}
// setupStdio 设置标准输入输出
func (gd *GoDaemon) setupStdio(cmd *exec.Cmd) error {
// 打开 /dev/null
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
return err
}
cmd.Stdin = devNull
cmd.Stdout = devNull
cmd.Stderr = devNull
return nil
}
// setupLogging 设置日志
func (gd *GoDaemon) setupLogging() error {
if gd.config.LogFile == "" {
return nil
}
// 确保日志目录存在
logDir := filepath.Dir(gd.config.LogFile)
if err := os.MkdirAll(logDir, 0755); err != nil {
return err
}
// 打开日志文件
logFile, err := os.OpenFile(gd.config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
// 设置日志输出
gd.logger.SetOutput(io.MultiWriter(logFile, os.Stdout))
return nil
}
// writePidFile 写入 PID 文件
func (gd *GoDaemon) writePidFile(pid int) error {
if gd.config.PidFile == "" {
return nil
}
// 确保 PID 文件目录存在
pidDir := filepath.Dir(gd.config.PidFile)
if err := os.MkdirAll(pidDir, 0755); err != nil {
return err
}
// 写入 PID 文件
return os.WriteFile(gd.config.PidFile, []byte(strconv.Itoa(pid)), 0644)
}
// removePidFile 删除 PID 文件
func (gd *GoDaemon) removePidFile() error {
if gd.config.PidFile == "" {
return nil
}
return os.Remove(gd.config.PidFile)
}
// isRunning 检查守护进程是否正在运行
func (gd *GoDaemon) isRunning() bool {
if gd.config.PidFile == "" {
return false
}
// 读取 PID 文件
pidData, err := os.ReadFile(gd.config.PidFile)
if err != nil {
return false
}
// 解析 PID
pidStr := strings.TrimSpace(string(pidData))
pid, err := strconv.Atoi(pidStr)
if err != nil {
return false
}
// 检查进程是否存在
process, err := os.FindProcess(pid)
if err != nil {
return false
}
// 发送信号 0 检查进程是否存活
err = process.Signal(syscall.Signal(0))
return err == nil
}
// Stop 停止守护进程
func (gd *GoDaemon) Stop() error {
if gd.config.PidFile == "" {
return fmt.Errorf("未配置 PID 文件")
}
// 读取 PID 文件
pidData, err := os.ReadFile(gd.config.PidFile)
if err != nil {
return fmt.Errorf("读取 PID 文件失败: %v", err)
}
// 解析 PID
pidStr := strings.TrimSpace(string(pidData))
pid, err := strconv.Atoi(pidStr)
if err != nil {
return fmt.Errorf("解析 PID 失败: %v", err)
}
// 查找进程
process, err := os.FindProcess(pid)
if err != nil {
return fmt.Errorf("查找进程失败: %v", err)
}
// 发送 SIGTERM 信号
if err := process.Signal(syscall.SIGTERM); err != nil {
return fmt.Errorf("发送终止信号失败: %v", err)
}
// 等待进程退出
for i := 0; i < 30; i++ {
if !gd.isRunning() {
fmt.Printf("守护进程 %s 已停止\n", gd.config.Name)
return nil
}
time.Sleep(1 * time.Second)
}
// 强制终止
if err := process.Signal(syscall.SIGKILL); err != nil {
return fmt.Errorf("强制终止失败: %v", err)
}
fmt.Printf("守护进程 %s 已强制停止\n", gd.config.Name)
return nil
}
// Status 检查守护进程状态
func (gd *GoDaemon) Status() {
if gd.isRunning() {
fmt.Printf("守护进程 %s 正在运行\n", gd.config.Name)
} else {
fmt.Printf("守护进程 %s 未运行\n", gd.config.Name)
}
}
// reloadConfig 重新加载配置
func (gd *GoDaemon) reloadConfig() {
gd.logger.Println("重新加载配置...")
// 这里添加配置重载逻辑
}
// printStatus 打印状态信息
func (gd *GoDaemon) printStatus() {
gd.logger.Printf("=== %s 状态信息 ===", gd.config.Name)
gd.logger.Printf("PID: %d", os.Getpid())
gd.logger.Printf("运行时间: %s", time.Since(startTime))
gd.logger.Printf("工作目录: %s", gd.config.WorkDir)
gd.logger.Printf("========================")
}
// cleanup 清理资源
func (gd *GoDaemon) cleanup() {
gd.logger.Println("清理资源...")
gd.removePidFile()
}
var startTime = time.Now()
func main() {
config := &DaemonConfig{
Name: "go-daemon",
Description: "Go 守护进程示例",
PidFile: "/tmp/go-daemon.pid",
LogFile: "/tmp/go-daemon.log",
WorkDir: "/",
}
daemon := NewGoDaemon(config)
if len(os.Args) < 2 {
fmt.Printf("用法: %s {start|stop|status|restart}\n", os.Args[0])
os.Exit(1)
}
switch os.Args[1] {
case "start":
if err := daemon.Start(); err != nil {
fmt.Printf("启动失败: %v\n", err)
os.Exit(1)
}
case "stop":
if err := daemon.Stop(); err != nil {
fmt.Printf("停止失败: %v\n", err)
os.Exit(1)
}
case "status":
daemon.Status()
case "restart":
daemon.Stop()
time.Sleep(2 * time.Second)
if err := daemon.Start(); err != nil {
fmt.Printf("重启失败: %v\n", err)
os.Exit(1)
}
default:
fmt.Printf("未知命令: %s\n", os.Args[1])
fmt.Printf("用法: %s {start|stop|status|restart}\n", os.Args[0])
os.Exit(1)
}
}
PID 文件管理 #
PID 文件操作 #
PID 文件是守护进程管理的重要组成部分:
package main
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)
// PidFileManager PID 文件管理器
type PidFileManager struct {
pidFile string
}
// NewPidFileManager 创建 PID 文件管理器
func NewPidFileManager(pidFile string) *PidFileManager {
return &PidFileManager{pidFile: pidFile}
}
// Write 写入 PID 文件
func (pfm *PidFileManager) Write(pid int) error {
// 确保目录存在
dir := filepath.Dir(pfm.pidFile)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
// 检查文件是否已存在
if pfm.Exists() {
if pfm.IsRunning() {
return fmt.Errorf("进程已在运行")
}
// 删除过期的 PID 文件
pfm.Remove()
}
// 写入 PID
content := fmt.Sprintf("%d\n", pid)
if err := os.WriteFile(pfm.pidFile, []byte(content), 0644); err != nil {
return fmt.Errorf("写入 PID 文件失败: %v", err)
}
return nil
}
// Read 读取 PID 文件
func (pfm *PidFileManager) Read() (int, error) {
data, err := os.ReadFile(pfm.pidFile)
if err != nil {
return 0, fmt.Errorf("读取 PID 文件失败: %v", err)
}
pidStr := strings.TrimSpace(string(data))
pid, err := strconv.Atoi(pidStr)
if err != nil {
return 0, fmt.Errorf("解析 PID 失败: %v", err)
}
return pid, nil
}
// Exists 检查 PID 文件是否存在
func (pfm *PidFileManager) Exists() bool {
_, err := os.Stat(pfm.pidFile)
return err == nil
}
// IsRunning 检查 PID 文件中的进程是否在运行
func (pfm *PidFileManager) IsRunning() bool {
pid, err := pfm.Read()
if err != nil {
return false
}
return pfm.IsProcessRunning(pid)
}
// IsProcessRunning 检查指定 PID 的进程是否在运行
func (pfm *PidFileManager) IsProcessRunning(pid int) bool {
process, err := os.FindProcess(pid)
if err != nil {
return false
}
// 发送信号 0 检查进程是否存活
err = process.Signal(syscall.Signal(0))
return err == nil
}
// Remove 删除 PID 文件
func (pfm *PidFileManager) Remove() error {
if !pfm.Exists() {
return nil
}
if err := os.Remove(pfm.pidFile); err != nil {
return fmt.Errorf("删除 PID 文件失败: %v", err)
}
return nil
}
// GetPid 获取当前 PID 文件中的 PID
func (pfm *PidFileManager) GetPid() (int, error) {
if !pfm.Exists() {
return 0, fmt.Errorf("PID 文件不存在")
}
return pfm.Read()
}
// Lock 锁定 PID 文件(防止多实例运行)
func (pfm *PidFileManager) Lock() (*os.File, error) {
file, err := os.OpenFile(pfm.pidFile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if err != nil {
if os.IsExist(err) {
return nil, fmt.Errorf("PID 文件已存在,可能有其他实例正在运行")
}
return nil, fmt.Errorf("创建 PID 文件失败: %v", err)
}
// 写入当前进程 PID
if _, err := fmt.Fprintf(file, "%d\n", os.Getpid()); err != nil {
file.Close()
os.Remove(pfm.pidFile)
return nil, fmt.Errorf("写入 PID 失败: %v", err)
}
return file, nil
}
// 示例用法
func demonstratePidFileManager() {
pidFile := "/tmp/demo-daemon.pid"
pfm := NewPidFileManager(pidFile)
fmt.Println("=== PID 文件管理示例 ===")
// 检查是否存在
fmt.Printf("PID 文件存在: %v\n", pfm.Exists())
// 写入当前进程 PID
currentPid := os.Getpid()
fmt.Printf("当前进程 PID: %d\n", currentPid)
if err := pfm.Write(currentPid); err != nil {
fmt.Printf("写入 PID 文件失败: %v\n", err)
return
}
fmt.Println("PID 文件写入成功")
// 读取 PID
if pid, err := pfm.Read(); err != nil {
fmt.Printf("读取 PID 文件失败: %v\n", err)
} else {
fmt.Printf("从 PID 文件读取的 PID: %d\n", pid)
}
// 检查进程是否在运行
fmt.Printf("进程是否在运行: %v\n", pfm.IsRunning())
// 清理
if err := pfm.Remove(); err != nil {
fmt.Printf("删除 PID 文件失败: %v\n", err)
} else {
fmt.Println("PID 文件已删除")
}
}
func main() {
demonstratePidFileManager()
}
守护进程最佳实践 #
完整的守护进程框架 #
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"sync"
"syscall"
"time"
)
// DaemonFramework 守护进程框架
type DaemonFramework struct {
config *DaemonConfig
logger *log.Logger
pidManager *PidFileManager
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
services []Service
}
// Service 服务接口
type Service interface {
Start(ctx context.Context) error
Stop() error
Name() string
}
// DaemonConfig 守护进程配置
type DaemonConfig struct {
Name string `json:"name"`
Description string `json:"description"`
PidFile string `json:"pid_file"`
LogFile string `json:"log_file"`
WorkDir string `json:"work_dir"`
User string `json:"user"`
Group string `json:"group"`
LogLevel string `json:"log_level"`
MaxLogSize int64 `json:"max_log_size"`
}
// NewDaemonFramework 创建守护进程框架
func NewDaemonFramework(configFile string) (*DaemonFramework, error) {
// 加载配置
config, err := loadConfig(configFile)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
return &DaemonFramework{
config: config,
pidManager: NewPidFileManager(config.PidFile),
ctx: ctx,
cancel: cancel,
services: make([]Service, 0),
}, nil
}
// loadConfig 加载配置文件
func loadConfig(configFile string) (*DaemonConfig, error) {
data, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %v", err)
}
var config DaemonConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %v", err)
}
// 设置默认值
if config.WorkDir == "" {
config.WorkDir = "/"
}
if config.LogLevel == "" {
config.LogLevel = "INFO"
}
return &config, nil
}
// RegisterService 注册服务
func (df *DaemonFramework) RegisterService(service Service) {
df.services = append(df.services, service)
}
// Start 启动守护进程
func (df *DaemonFramework) Start() error {
if df.isDaemonMode() {
return df.runDaemon()
}
// 检查是否已经在运行
if df.pidManager.IsRunning() {
return fmt.Errorf("守护进程已经在运行")
}
return df.startDaemon()
}
// isDaemonMode 检查是否在守护进程模式
func (df *DaemonFramework) isDaemonMode() bool {
return os.Getenv("DAEMON_MODE") == "1"
}
// startDaemon 启动守护进程
func (df *DaemonFramework) startDaemon() error {
executable, err := os.Executable()
if err != nil {
return err
}
cmd := exec.Command(executable, os.Args[1:]...)
cmd.Env = append(os.Environ(), "DAEMON_MODE=1")
cmd.Dir = df.config.WorkDir
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
// 重定向输入输出
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
return err
}
defer devNull.Close()
cmd.Stdin = devNull
cmd.Stdout = devNull
cmd.Stderr = devNull
if err := cmd.Start(); err != nil {
return err
}
// 写入 PID 文件
if err := df.pidManager.Write(cmd.Process.Pid); err != nil {
cmd.Process.Kill()
return err
}
fmt.Printf("守护进程 %s 已启动,PID: %d\n", df.config.Name, cmd.Process.Pid)
return nil
}
// runDaemon 运行守护进程
func (df *DaemonFramework) runDaemon() error {
// 设置日志
if err := df.setupLogging(); err != nil {
return err
}
df.logger.Printf("守护进程 %s 启动,PID: %d", df.config.Name, os.Getpid())
// 写入 PID 文件
if err := df.pidManager.Write(os.Getpid()); err != nil {
return err
}
// 设置信号处理
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
// 启动所有服务
if err := df.startServices(); err != nil {
return err
}
// 等待信号
sig := <-sigChan
df.logger.Printf("接收到信号: %s", sig)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
df.logger.Println("开始优雅关闭...")
df.shutdown()
case syscall.SIGHUP:
df.logger.Println("重新加载配置...")
df.reload()
}
return nil
}
// setupLogging 设置日志
func (df *DaemonFramework) setupLogging() error {
var writers []io.Writer
// 添加标准输出(仅在非守护进程模式下)
if !df.isDaemonMode() {
writers = append(writers, os.Stdout)
}
// 添加日志文件
if df.config.LogFile != "" {
logDir := filepath.Dir(df.config.LogFile)
if err := os.MkdirAll(logDir, 0755); err != nil {
return err
}
logFile, err := os.OpenFile(df.config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
writers = append(writers, logFile)
}
if len(writers) == 0 {
writers = append(writers, os.Stdout)
}
df.logger = log.New(io.MultiWriter(writers...), fmt.Sprintf("[%s] ", df.config.Name), log.LstdFlags)
return nil
}
// startServices 启动所有服务
func (df *DaemonFramework) startServices() error {
for _, service := range df.services {
df.logger.Printf("启动服务: %s", service.Name())
df.wg.Add(1)
go func(svc Service) {
defer df.wg.Done()
if err := svc.Start(df.ctx); err != nil {
df.logger.Printf("服务 %s 启动失败: %v", svc.Name(), err)
}
}(service)
}
return nil
}
// shutdown 关闭守护进程
func (df *DaemonFramework) shutdown() {
df.logger.Println("开始关闭所有服务...")
// 取消上下文
df.cancel()
// 停止所有服务
for _, service := range df.services {
df.logger.Printf("停止服务: %s", service.Name())
if err := service.Stop(); err != nil {
df.logger.Printf("停止服务 %s 失败: %v", service.Name(), err)
}
}
// 等待所有服务停止
done := make(chan struct{})
go func() {
df.wg.Wait()
close(done)
}()
select {
case <-done:
df.logger.Println("所有服务已停止")
case <-time.After(30 * time.Second):
df.logger.Println("等待服务停止超时")
}
// 清理 PID 文件
df.pidManager.Remove()
df.logger.Println("守护进程已关闭")
}
// reload 重新加载配置
func (df *DaemonFramework) reload() {
df.logger.Println("重新加载配置...")
// 这里可以添加配置重载逻辑
}
// Stop 停止守护进程
func (df *DaemonFramework) Stop() error {
if !df.pidManager.IsRunning() {
return fmt.Errorf("守护进程未运行")
}
pid, err := df.pidManager.GetPid()
if err != nil {
return err
}
process, err := os.FindProcess(pid)
if err != nil {
return err
}
// 发送 SIGTERM 信号
if err := process.Signal(syscall.SIGTERM); err != nil {
return err
}
// 等待进程退出
for i := 0; i < 30; i++ {
if !df.pidManager.IsRunning() {
fmt.Printf("守护进程 %s 已停止\n", df.config.Name)
return nil
}
time.Sleep(1 * time.Second)
}
// 强制终止
process.Signal(syscall.SIGKILL)
fmt.Printf("守护进程 %s 已强制停止\n", df.config.Name)
return nil
}
// Status 检查状态
func (df *DaemonFramework) Status() {
if df.pidManager.IsRunning() {
pid, _ := df.pidManager.GetPid()
fmt.Printf("守护进程 %s 正在运行 (PID: %d)\n", df.config.Name, pid)
} else {
fmt.Printf("守护进程 %s 未运行\n", df.config.Name)
}
}
// 示例服务实现
type ExampleService struct {
name string
}
func NewExampleService(name string) *ExampleService {
return &ExampleService{name: name}
}
func (es *ExampleService) Name() string {
return es.name
}
func (es *ExampleService) Start(ctx context.Context) error {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Printf("服务 %s 正在工作...", es.name)
case <-ctx.Done():
log.Printf("服务 %s 收到停止信号", es.name)
return nil
}
}
}
func (es *ExampleService) Stop() error {
log.Printf("服务 %s 正在停止...", es.name)
return nil
}
func main() {
if len(os.Args) < 3 {
fmt.Printf("用法: %s <config-file> {start|stop|status|restart}\n", os.Args[0])
os.Exit(1)
}
configFile := os.Args[1]
command := os.Args[2]
daemon, err := NewDaemonFramework(configFile)
if err != nil {
fmt.Printf("创建守护进程失败: %v\n", err)
os.Exit(1)
}
// 注册服务
daemon.RegisterService(NewExampleService("worker1"))
daemon.RegisterService(NewExampleService("worker2"))
switch command {
case "start":
if err := daemon.Start(); err != nil {
fmt.Printf("启动失败: %v\n", err)
os.Exit(1)
}
case "stop":
if err := daemon.Stop(); err != nil {
fmt.Printf("停止失败: %v\n", err)
os.Exit(1)
}
case "status":
daemon.Status()
case "restart":
daemon.Stop()
time.Sleep(2 * time.Second)
if err := daemon.Start(); err != nil {
fmt.Printf("重启失败: %v\n", err)
os.Exit(1)
}
default:
fmt.Printf("未知命令: %s\n", command)
os.Exit(1)
}
}
小结 #
守护进程开发是系统编程的重要技能。通过本节学习,我们掌握了:
- 守护进程基础:了解了守护进程的特征和创建步骤
- Go 实现方式:学会了在 Go 中实现守护进程的方法
- PID 文件管理:掌握了进程管理的最佳实践
- 完整框架:构建了可复用的守护进程框架
这些技术为开发稳定可靠的系统服务奠定了基础,是构建企业级应用程序的重要技能。