今天我们来一起来学习一下golang一个第三方进程管理工具goreman
其功能和supervisor类似,用于管理多个进程
github地址如下:
https://github.com/mattn/goreman
一、命令行参数
查看所有参数,最直接方法是
goreman help
常用命令
goreman start //启动所有进程goreman run start COMMAND //启动一个进程goreman run stop COMMAND //停止一个进程goreman run list //查看goreman运行哪些进程goreman run status //查看进程状态,带*的是运行中goreman run restart/restart-all/stop/stop-all
goreman启动时依赖的配置文件是Procfile,来看看这个文件长什么样
web1: ./server -addr :9000web2: ./server -addr :9001
web1是为进程起的名字,其后是该进程启动命令
编写一个简单的示例server
package mainimport ("fmt""net/http""flag")var (Addr string)func indexHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "hello world")}func main() {http.HandleFunc("/", indexHandler)http.ListenAndServe(Addr, nil)}func init() {flag.StringVar(&Addr, "addr", ":8088", "addr")flag.Parse()}
程序接受一个参数:地址,作为httpserver的启动地址,提供服务,输出hello world
启动goreman
$ goreman start15:07:20 web1 | Starting web1 on port 500015:07:20 web2 | Starting web2 on port 510015:07:20 web1 | :900015:07:20 web2 | :9001
问题:
-
进程启动时,Starting web1 on port 5000是什么意思?
-
命令行参数中有一个可选参数port是什么port?
-
内部是如何启动各个进程的?
-
stop某个进程时是如何操作的?
-
goreman run status命令是怎么检测各个进程状态的?
带着这些问题,读源码一探究竟
二、整体逻辑梳理
goreman代码结构比较简单
主要逻辑:goreman.go
进程相关:proc.go
rpc定义:rpc.go
2.1 关键结构
type config struct {Procfile string `yaml:"procfile"`// Port for RPC serverPort uint `yaml:"port"` // rpc端口号BaseDir string `yaml:"basedir"`BasePort uint `yaml:"baseport"`...}// -- process information structure.type procInfo struct {name string // 进程namecmdline string // 进程命令行cmd *exec.Cmdport uint // 启动端口setPort boolcolorIndex int // 命令编号...}
2.2 主要逻辑
main()readConfig() // 读取并初始化配置(Procfile、port、baseport等)start()readProcfile() // 解析procfile文件,把进程名和启动命令分别解析为k,vgo startServer() // 在port端口启动一个rpc server,接收rpc请求,默认端口号8555startProcs() // 遍历所有procs命令,在routine里启动一个个进程//接受终止Ctrl+c命令或者stop命令退出
2.3 rpc命令处理
以stop命令为例:当执行goremon run stop web1命令时,实际调用的是goremon的对应的rpc stop方法,找到proc.cmd.Process的pid对应的进程,给进程发送终止信号os.Interrupt,从而结束进程
func run(cmd string, args []string, serverPort uint) error {client, err := rpc.Dial("tcp", defaultServer(serverPort))if err != nil {return err}defer client.Close()var ret stringswitch cmd {case "start":return client.Call("Goreman.Start", args, &ret)case "stop":return client.Call("Goreman.Stop", args, &ret)case "stop-all":return client.Call("Goreman.StopAll", args, &ret)case "restart":return client.Call("Goreman.Restart", args, &ret)case "restart-all":return client.Call("Goreman.RestartAll", args, &ret)case "list":err := client.Call("Goreman.List", args, &ret)fmt.Print(ret)return errcase "status":err := client.Call("Goreman.Status", args, &ret)fmt.Print(ret)return err}return errors.New("unknown command")}// Stop do stopfunc (r *Goreman) Stop(args []string, ret *string) (err error) {defer func() {if r := recover(); r != nil {err = r.(error)}}()errChan := make(chan error, 1)r.rpcChan <- &rpcMessage{Msg: "stop",Args: args,ErrCh: errChan,}err = <-errChanreturn}// 主进程从channel里读取rpcMsg处理,目前看只支持stop命令// 其他命令实现是直接调用方法来处理,比如start web1,直接调用startProc方法select {case rpcMsg := <-rpcCh:switch rpcMsg.Msg {case "stop":for _, proc := range rpcMsg.Args {if err := stopProc(proc, nil); err != nil {rpcMsg.ErrCh <- errbreak}}close(rpcMsg.ErrCh)default:panic("unimplemented rpc message type " + rpcMsg.Msg)}...}func terminateProc(proc *procInfo, signal os.Signal) error {p := proc.cmd.Processif p == nil {return nil}pgid, err := unix.Getpgid(p.Pid)if err != nil {return err}pid := p.Pidif pgid == p.Pid {pid = -1 * pid}target, err := os.FindProcess(pid)if err != nil {return err}// 给process发送signalreturn target.Signal(signal)}
2.4 再看看start命令是怎么实现的
// 直接调用startProc方法,创建进程,这个方法也是goremon程序启动时调用的方法func (r *Goreman) Start(args []string, ret *string) (err error) {defer func() {if r := recover(); r != nil {err = r.(error)}}()for _, arg := range args {if err = startProc(arg, nil, nil); err != nil {break}}return err}
三、问题解答
-
进程启动时,Starting web1 on port 5000是什么意思?
每个进程的port env环境变量(暂时没发现有什么用)
-
命令行参数中有一个可选参数port是什么port?
rpc服务监听的端口号。
-
内部是如何启动各个进程的?
见2.2部分。
-
stop某个进程时是如何操作的?
见2.3部分。
-
goreman run status命令是怎么检测各个进程状态的?
通过判断procs里存储的cmd是否为nil来判断进程状态,如果proc.cmd不为nil,就加个*标识运行中;如果进程被stop了,那么cmd为nil
func (r *Goreman) Status(args []string, ret *string) (err error) {defer func() {if r := recover(); r != nil {err = r.(error)}}()*ret = ""for _, proc := range procs {if proc.cmd != nil {*ret += "*" + proc.name + "\n"} else {*ret += " " + proc.name + "\n"}}return err}
$ goreman run status*web1*web2$ goreman run stop web1$ goreman run statusweb1*web2
总结:
相较于linux的supervisor工具,goremon更简单易用,管理进程
文章来自微信专栏:迷人的少侠
Golang进程管理利器goreman

本文介绍goreman,一个Golang第三方进程管理工具,功能类似supervisor,用于管理多个进程。文章详细解析了goreman的命令行参数、内部启动与停止进程的机制,以及如何检测进程状态。
575

被折叠的 条评论
为什么被折叠?



