golang 实现 linux 进程管理,管理php后台任务 - 第三节:核心代码部分

接着上一篇:

  1. 通过golang 实现 进程守护,进程挂掉后,自动拉起新进程
  2. 所有配置进程一键启动 ,一键关闭
  3. 进程数量控制,如:php a.php 开启2个
  4. 多进程数量控制,相同命令配置有多个时,缺少一个时,自动新启动
  5. 监控配置文件,配置有变化时,自动更新
  6. 配置分离,测试和线上配置分开
  7. 程序编译为二进制后体积小: 约4.2M
  8. 系统开销极小
  9. 无依赖:一个文件+配置文件即可

本文主要为核心代码部分

cmd/daemon-process.go

package main

import (
	"daemon-process/internal/conf/constant"
	"daemon-process/internal/engine"
	"flag"
)

func init() {
	flag.StringVar(&constant.ConfigDir, "configDir", "", "Config path")
	flag.BoolVar(&constant.Debug, "debug", false, "Debug mode")
	flag.StringVar(&constant.Env, "env", "dev", "env")
	flag.Parse()
}
func main() {
	app := engine.NewEngine()
	app.DaemonProcess.Run()
}

configs/config-dev.yaml

#日志目录
LogDir: "xxxx"
#单位s
Interval: 30
ProcessList:
  - ProcessName: "测试"
    ProcessNum: 1
    Command: "ping www.baidu.com"
    User: "root"
  - ProcessName: "测试2"
    ProcessNum: 1
    Command: "ping www.qq.com"
    User: "root"

internal/conf/conf.go

package conf

import (
	"daemon-process/internal/conf/constant"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"log"
	"sync"
)

type AppConfig struct {
	LogDir      string        // 日志目录
	Interval    int           // 间隔时间,单位秒
	ProcessList []ProcessList // 进程列表
}
type ProcessList struct {
	ProcessName string `json:"ProcessName"`
	ProcessNum  int    `json:"ProcessNum"`
	Command     string `json:"Command"`
	User        string `json:"User"`
}

var (
	appConfigInstance     *AppConfig
	appConfigInstanceOnce sync.Once
)

func GetAppConfigInstance() *AppConfig {

	appConfigInstanceOnce.Do(func() {
		log.Println("Load config")

		var newAppConfig AppConfig

		viper.SetConfigName("config-" + constant.Env)
		viper.AddConfigPath(constant.ConfigDir)
		viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name

		err := viper.ReadInConfig() // Find and read the config file
		if err != nil {             // Handle errors reading the config file
			log.Fatalf("fatal error config file: %+v", err)
		}
		err = viper.Unmarshal(&newAppConfig)
		if err != nil {
			log.Fatalf("config Unmarshal err: %+v", err)
		}

		viper.OnConfigChange(func(e fsnotify.Event) {
			log.Println("Config file changed:", e.Name)
			err = viper.Unmarshal(&newAppConfig)
			if err != nil {
				log.Fatalf("config Unmarshal err: %+v", err)
			}
			appConfigInstance = &newAppConfig
		})
		viper.WatchConfig()
		appConfigInstance = &newAppConfig
	})

	return appConfigInstance
}
func GetConfig() *AppConfig {
	return GetAppConfigInstance()
}

internal/core/daemon/function.go

package daemon

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"syscall"
)

func read(done chan bool, std io.ReadCloser, p *os.Process) {
	reader := bufio.NewReader(std)

	for {
		select {
		case <-done:
			return
		default:
			readString, err := reader.ReadString('\n')
			if err != nil || err == io.EOF {
				p.Signal(syscall.SIGQUIT)
				return
			}
			fmt.Print(readString)
		}
	}
}

internal/core/daemon/process.go

package daemon

import (
	"context"
	"daemon-process/internal/conf"
	"daemon-process/internal/utils"
	"fmt"
	"github.com/shirou/gopsutil/v3/process"
	"log"
	"os/exec"
	"sync"
	"time"
)

type Client struct {
	AppConfig *conf.AppConfig
}

var (
	daemonProcessInstance     *Client
	daemonProcessInstanceOnce sync.Once
	ProcessList               map[string]int
)

func NewClient() *Client {
	daemonProcessInstanceOnce.Do(func() {
		var client Client
		client.AppConfig = conf.GetConfig()
		daemonProcessInstance = &client
	})
	return daemonProcessInstance
}

func (sf Client) Run() {
	for {
		// 获取进程列表
		totalErrProcess := 0
		processListData := make(map[string]int, 0)
		procs, err := process.Processes()
		for _, proc := range procs {
			// var exeName string
			var cmdLine string

			exeName, _ := proc.Exe()
			cmdLine, err = proc.Cmdline()
			if err != nil {
				cmdLine = "Error: " + err.Error()
			}

			if cmdLine == "" {
				continue
			}
			if exeName == "" {
				// Error: invalid argument
				fmt.Printf("Error_Process #%v: Name: %v / CmdLine: %v\n", proc.Pid, exeName, cmdLine)
				// 通过计数判断错误数量,超过10个,则不再进行启动任务,需要解决有问题的任务配置
				totalErrProcess++
			}
			cmdLineKey := utils.Md5(cmdLine)
			processListData[cmdLineKey] += 1

		}
		if totalErrProcess > 10 {
			fmt.Println("错误进程太多,需要解决进程配置中有问题的命令,然后执行:", "service vote-webapps restart")
			time.Sleep(time.Duration(sf.AppConfig.Interval) * time.Second)
			continue
		}
		ProcessList = processListData
		for _, p := range sf.AppConfig.ProcessList {
			sf.Task(p)
		}
		time.Sleep(time.Duration(sf.AppConfig.Interval) * time.Second)
	}
}
func (sf Client) Task(cmdCfg conf.ProcessList) {
	if cmdCfg.User == "root" {
		cmdLineKey := utils.Md5(cmdCfg.Command)
		processNum := 0
		if data, ok := ProcessList[cmdLineKey]; ok {
			processNum = data
		}
		for i := processNum; i < cmdCfg.ProcessNum; i++ {
			log.Printf("启动进程: %s , 执行命令: %s ,进程数量: %d \n", cmdCfg.ProcessName, cmdCfg.Command, cmdCfg.ProcessNum)
			go sf.execWithContext(cmdCfg)
			time.Sleep(50 * time.Millisecond)
		}
	} else {
		// sf.execWithUser(cmdCfg)
	}
}

func (sf Client) execWithContext(cmdCfg conf.ProcessList) error {
	ctx, _ := context.WithCancel(context.Background())
	cmd := exec.CommandContext(ctx, "/bin/bash", "-c", cmdCfg.Command)
	// cmd := exec.Cmd{}
	args := cmd.Args

	if args == nil {
		args = []string{cmd.Path}
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	if err := cmd.Start(); err != nil {
		log.Printf("starting %s: %v", args, err)
	}
	p := cmd.Process
	done := make(chan bool)

	go read(done, stderr, p)
	go read(done, stdout, p)

	err = cmd.Wait()
	if err != nil {
		log.Printf("%s exit status: %v", args, err)
	}
	close(done)
	return nil
}

如需完整代码,请关注我 或 继续看其它章节。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值