golang 源碼分析:goreman
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(
執行命令。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/seyqZXFJrZZc4OAfdokvkw