https://github.com/mattn/goreman是golang实现的一个进程管理器,方便我们快速启动多个进程,比如etcd就是通过它来管理的,下面我们看下如何使用:
新建一个文件Procfile
command1: ls
command2: pwd
command3: echo 'helloworld'
然后,运行命令
% goreman start
18:44:10 command3 | Starting command3 on port 5200
18:44:10 command1 | Starting command1 on port 5000
18:44:10 command2 | Starting command2 on port 5100
18:44:10 command3 | helloworld
18:44:10 command3 | Terminating command3
18:44:10 command2 | /test/goreman/exp1
18:44:10 command2 | Terminating command2
18:44:10 command1 | Procfile
18:44:10 command1 | Terminating command1
看完如何使用后我们分析下它的源码:入口文件位于main.go,首先是加载配置文件,然后切换到base目录,然后就是根据提示执行命令,我们重点看下start
func main() {
cfg := readConfig()
if cfg.BaseDir != "" {
err = os.Chdir(cfg.BaseDir)
cmd := cfg.Args[0]
switch cmd {
case "check":
err = check(cfg)
case "help":
usage()
case "run":
if len(cfg.Args) >= 2 {
cmd, args := cfg.Args[1], cfg.Args[2:]
err = run(cmd, args, cfg.Port)
} else {
usage()
}
case "export":
if len(cfg.Args) == 3 {
format, path := cfg.Args[1], cfg.Args[2]
err = export(cfg, format, path)
} else {
usage()
}
case "start":
c := notifyCh()
err = start(context.Background(), c, cfg)
case "version":
showVersion()
default:
usage()
}
// filename of Procfile.
var procfile = flag.String("f", "Procfile", "proc file")
func readConfig() *config {
var cfg config
flag.Parse()
if flag.NArg() == 0 {
usage()
}
cfg.Procfile = *procfile
首先是监听命令行的信号
func notifyCh() <-chan os.Signal {
sc := make(chan os.Signal, 10)
signal.Notify(sc, sigterm, sigint, sighup)
return sc
}
然后通过chan传给start函数
err = start(context.Background(), c, cfg)
首先解析我们的Procfile
func readProcfile(cfg *config) error {
content, err := os.ReadFile(cfg.Procfile)
for _, line := range strings.Split(string(content), "\n") {
tokens := strings.SplitN(line, ":", 2)
proc := &procInfo{name: k, cmdline: v, colorIndex: index}
if *setPorts {
proc.setPort = true
proc.port = cfg.BasePort
cfg.BasePort += 100
}
proc.cond = sync.NewCond(&proc.mu)
然后查找proc,分类启动命令
func start(ctx context.Context, sig <-chan os.Signal, cfg *config) error {
err := readProcfile(cfg)
for _, v := range cfg.Args[1:] {
proc := findProc(v)
if *startRPCServer {
go startServer(ctx, rpcChan, cfg.Port)
}
procsErr := startProcs(sig, rpcChan, cfg.ExitOnError)
启动rpc的代码位于rpc.go
func startServer(ctx context.Context, rpcChan chan<- *rpcMessage, listenPort uint) error {
rpc.Register(gm)
server, err := net.Listen("tcp", fmt.Sprintf("%s:%d", defaultAddr(), listenPort))
启动其它进程的代码位于proc.go
func startProcs(sc <-chan os.Signal, rpcCh <-chan *rpcMessage, exitOnError bool) error {
for _, proc := range procs {
startProc(proc.name, &wg, errCh)
}
func startProc(name string, wg *sync.WaitGroup, errCh chan<- error) error {
proc := findProc(name)
go func() {
spawnProc(name, errCh)
if wg != nil {
wg.Done()
}
proc.mu.Unlock()
}()
func spawnProc(name string, errCh chan<- error) {
proc := findProc(name)
logger := createLogger(name, proc.colorIndex)
cs := append(cmdStart, proc.cmdline)
cmd := exec.Command(cs[0], cs[1:]...)
cmd.Stdin = nil
cmd.Stdout = logger
cmd.Stderr = logger
cmd.SysProcAttr = procAttrs
最终调用
cmd := exec.Command(
执行命令。