【源码阅读】goreman

今天我们来一起来学习一下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

 

 

问题:

  1. 进程启动时,Starting web1 on port 5000是什么意思?

  2. 命令行参数中有一个可选参数port是什么port?

  3. 内部是如何启动各个进程的?

  4. stop某个进程时是如何操作的?

  5. goreman run status命令是怎么检测各个进程状态的?

 

带着这些问题,读源码一探究竟

 

二、整体逻辑梳理

goreman代码结构比较简单

主要逻辑:goreman.go

进程相关:proc.go

rpc定义:rpc.go

 

2.1 关键结构

type config struct {  Procfile string `yaml:"procfile"`  // Port for RPC server  Port     uint   `yaml:"port"`     // rpc端口号  BaseDir  string `yaml:"basedir"`  BasePort uint   `yaml:"baseport"`  ...}// -- process information structure.type procInfo struct {  name       string   // 进程name  cmdline    string   // 进程命令行  cmd        *exec.Cmd  port       uint     // 启动端口  setPort    bool  colorIndex int      // 命令编号  ...}

 

 

2.2 主要逻辑

main() readConfig() // 读取并初始化配置(Procfile、port、baseport等) start()  readProcfile() // 解析procfile文件,把进程名和启动命令分别解析为k,v  go startServer() // 在port端口启动一个rpc server,接收rpc请求,默认端口号8555  startProcs()    // 遍历所有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 string  switch 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 err  case "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 = <-errChan  return}// 主进程从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 <- err            break          }        }        close(rpcMsg.ErrCh)      default:        panic("unimplemented rpc message type " + rpcMsg.Msg)      }      ...   }func terminateProc(proc *procInfo, signal os.Signal) error {  p := proc.cmd.Process  if p == nil {    return nil  }  pgid, err := unix.Getpgid(p.Pid)  if err != nil {    return err  }  pid := p.Pid  if pgid == p.Pid {    pid = -1 * pid  }  target, err := os.FindProcess(pid)  if err != nil {    return err  }  // 给process发送signal  return 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}

 

三、问题解答

  1. 进程启动时,Starting web1 on port 5000是什么意思?

    每个进程的port env环境变量(暂时没发现有什么用)

  2. 命令行参数中有一个可选参数port是什么port?

    rpc服务监听的端口号。

  3. 内部是如何启动各个进程的?

    见2.2部分。

  4. stop某个进程时是如何操作的?

    见2.3部分。

  5. 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 status web1*web2

 

总结:

相较于linux的supervisor工具,goremon更简单易用,管理进程

 

文章来自微信专栏:迷人的少侠

 

你可能感兴趣的:(编程,golang)