Golang守護(hù)進(jìn)程用法示例分析
前言
golang實(shí)現(xiàn)守護(hù)進(jìn)程,包含功能:
1. 守護(hù)進(jìn)程只創(chuàng)建一次
2. 平滑創(chuàng)建業(yè)務(wù)進(jìn)程
3. 業(yè)務(wù)進(jìn)程掛起,守護(hù)進(jìn)程能監(jiān)聽(tīng),并重啟新啟業(yè)務(wù)進(jìn)程
4. 守護(hù)進(jìn)程退出,也能保證業(yè)務(wù)進(jìn)程退出
5. 業(yè)務(wù)進(jìn)程≈子進(jìn)程
6. 不影響業(yè)務(wù)進(jìn)程邏輯
7. 以Linux平臺(tái)為主,其他平臺(tái)暫時(shí)沒(méi)有實(shí)施條件
分析
上一篇博文討論過(guò)如何以腳本的形式創(chuàng)建守護(hù)進(jìn)程,這篇討論如何以純golang腳本實(shí)現(xiàn)守護(hù)進(jìn)程的功能
- 在 Unix 中,創(chuàng)建一個(gè)進(jìn)程,通過(guò)系統(tǒng)調(diào)用 fork 實(shí)現(xiàn)(及其一些變種,如 vfork、clone)。
- 在 Go 語(yǔ)言中,Linux 下創(chuàng)建進(jìn)程使用的系統(tǒng)調(diào)用是 clone 。
在 C 語(yǔ)言中,通常會(huì)用到 2 種創(chuàng)建進(jìn)程方法:
fork
pid = fork(); //pid > 0 父進(jìn)程 //pid = 0 子進(jìn)程 //pid < 0 出錯(cuò)
程序會(huì)從 fork 處一分為二,父進(jìn)程返回值大于0,并繼續(xù)運(yùn)行;子進(jìn)程獲得父進(jìn)程的棧、數(shù)據(jù)段、堆和執(zhí)行文本段的拷貝,返回值等于0,并向下繼續(xù)運(yùn)行。通過(guò) fork 返回值可輕松判斷當(dāng)前處于父進(jìn)程還是子進(jìn)程。
execve
execve(pathname, argv, envp); //pathname 可執(zhí)行文件路徑 //argv 參數(shù)列表 //envp 環(huán)境變量列表
execve 為加載一個(gè)新程序到當(dāng)前進(jìn)程的內(nèi)存,這將丟棄現(xiàn)存的程序文本段,并為新程序重新創(chuàng)建棧、數(shù)據(jù)段以及堆。通常將這一動(dòng)作稱為執(zhí)行一個(gè)新程序。
在 Go 語(yǔ)言中,創(chuàng)建進(jìn)程方法主要有 3 種:
exec.Command
//判 斷當(dāng)其是否是子進(jìn)程,當(dāng)父進(jìn)程return之后,子進(jìn)程會(huì)被 系統(tǒng)1 號(hào)進(jìn)程接管 if os.Getppid() != 1 { // 將命令行參數(shù)中執(zhí)行文件路徑轉(zhuǎn)換成可用路徑 filePath, _ := filepath.Abs(os.Args[0]) cmd := exec.Command(filePath, os.Args[1:]...) // 將其他命令傳入生成出的進(jìn)程 cmd.Stdin = os.Stdin // 給新進(jìn)程設(shè)置文件描述符,可以重定向到文件中 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Start() // 開(kāi)始執(zhí)行新進(jìn)程,不等待新進(jìn)程退出 os.Exit(0) }
os.StartProcess
if os.Getppid()!=1{ args:=append([]string{filePath},os.Args[1:]...) os.StartProcess(filePath,args,&os.ProcAttr{Files:[]*os.File{os.Stdin,os.Stdout,os.Stderr}}) os.Exit(0) }
syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
pid, _, sysErr := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) if sysErr != 0 { Utils.LogErr(sysErr) os.Exit(0) }
方法1和方法2通過(guò) os.Getppid()!=1進(jìn)行判斷是否子進(jìn)程,默認(rèn)父進(jìn)程退出之后,子進(jìn)程會(huì)被1號(hào)進(jìn)程接管。
但據(jù)Ubuntu Desktop 本地測(cè)試,接管孤兒進(jìn)程的并不是1號(hào)進(jìn)程,因此考慮到程序穩(wěn)定性和兼容性,不能夠以 ppid 作為判斷父子進(jìn)程的依據(jù)。
方法3直接進(jìn)行了系統(tǒng)調(diào)用,雖然可以通過(guò) pid 進(jìn)行判斷父子進(jìn)程,但該方法過(guò)于底層。
綜上,以exec.Command方式,通過(guò)控制參數(shù)實(shí)現(xiàn)守護(hù)進(jìn)程
實(shí)現(xiàn)
func main() { // ------------------------ 守護(hù)進(jìn)程 start ------------------------ basePath, _ := os.Getwd() baseDir := filepath.Dir(basePath) fmt.Println(fmt.Sprintf("basePath is %s and baseDir is %s", basePath, baseDir)) // step1 // 創(chuàng)建監(jiān)聽(tīng)退出chan c := make(chan os.Signal) // 監(jiān)聽(tīng)指定信號(hào) ctrl+c kill signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func() { for s := range c { switch s { case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT: utils.StopBusinessProcess(fmt.Sprintf("go_start | grep business")) os.Exit(0) default: fmt.Println("test stop others...") } } }() fmt.Println(fmt.Sprintf("os.args is %v", os.Args)) join := strings.Join(os.Args, "") // step2 if !strings.Contains(join, "-daemon") { fmt.Println("enter daemon branch...") isE, ierr := utils.CheckProRunning("go_start | grep daemon") if ierr != nil { fmt.Println("check daemon process failed, " + ierr.Error()) return } if isE { fmt.Println("daemon process exist!") } else { fmt.Println("start daemon process...") // 啟動(dòng)守護(hù)進(jìn)程 cmd := exec.Command(os.Args[0], "-c", os.Args[2], "-d", os.Args[4], "-e", os.Args[6], "-daemon") cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr strerr := cmd.Start() if strerr != nil { fmt.Println("start daemon process fail," + strerr.Error()) return } fmt.Println("start daemon process success!") time.Sleep(time.Second * 2) daePid := cmd.Process.Pid isDae, daeErr := utils.CheckProRunning("go_start | grep daemon") if daeErr != nil { fmt.Println("check daemon process failed, " + daeErr.Error()) return } if isDae { fmt.Println(fmt.Sprintf("start daemon process success, pid is %d", daePid)) return } else { fmt.Println("warning! start business process fail...") } } } // step3 join = strings.Join(os.Args, "") if strings.Contains(join, "-daemon") { fmt.Println("enter business branch...") for { exist, checkerr := utils.CheckProRunning("go_start | grep business") if checkerr != nil { fmt.Println("check business failed, " + checkerr.Error()) return } if exist { fmt.Println("business process exist!") time.Sleep(time.Second * 5) continue } fmt.Println("start business process...") command := exec.Command(fmt.Sprintf(fmt.Sprintf("%s/go_start", basePath), "-business", "-c", os.Args[2], "-d", os.Args[4], "-e", os.Args[6])) command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if comerr := command.Start(); comerr != nil { fmt.Println("start business process failed, " + comerr.Error()) return } time.Sleep(time.Second * 5) businessPid := command.Process.Pid exist, checkerr = utils.CheckProRunning("go_start | grep business") if checkerr != nil { fmt.Println("check business process failed, " + checkerr.Error()) return } if exist { fmt.Println(fmt.Sprintf("start business process suceess, pid is %d", businessPid)) } else { fmt.Println("warning! start business process fail...") } } } // ------------------------ 守護(hù)進(jìn)程 end ------------------------ // ------------------------ 業(yè)務(wù)進(jìn)程 start ------------------------ fmt.Println("hello, welcome to business detail!") }
相關(guān)工具方法:
package utils import ( "os" "fmt" "go_start/core/global" "os/exec" "runtime" "strconv" "strings" "syscall" ) func StopBusinessProcess(serverName string) { global.G_LOG.Info("start to stop business...") pid, _ := GetPid(serverName) if pid > 0 { global.G_LOG.Info(fmt.Sprintf("stop %s ...", serverName)) syscall.Kill(pid, syscall.SIGKILL) global.G_LOG.Info(fmt.Sprintf("stop business success, pid is %d", pid)) } } //根據(jù)進(jìn)程名判斷進(jìn)程是否運(yùn)行 func CheckProRunning(serverName string) (bool, error) { a := `ps -ef|grep ` + serverName + `|grep -v grep|awk '{print $2}'` pid, err := runCommand(a) if err != nil { return false, err } return pid != "", nil } //根據(jù)進(jìn)程名稱獲取進(jìn)程ID func GetPid(serverName string) (pid int, err error) { a := `ps -ef|grep ` + serverName + `|grep -v grep|awk '{print $2}'` var pidStr string if pidStr, err = runCommand(a); err != nil { return } pid, err = strconv.Atoi(pidStr) return } func runCommand(cmd string) (string, error) { if runtime.GOOS == "windows" { return runInWindows(cmd) } else { return runInLinux(cmd) } } func runInWindows(cmd string) (string, error) { result, err := exec.Command("cmd", "/c", cmd).Output() if err != nil { return "", err } return strings.TrimSpace(string(result)), err } func runInLinux(cmd string) (string, error) { result, err := exec.Command("/bin/sh", "-c", cmd).Output() if err != nil { return "", err } return strings.TrimSpace(string(result)), err }
說(shuō)明
1、啟動(dòng)go_start二進(jìn)制文件,方式:./go_start -c param1 -d param2 -e param3,這里第一次進(jìn)入main方法
2、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3],此時(shí)不包含"-daemon"參數(shù),進(jìn)入step2,走創(chuàng)建守護(hù)進(jìn)程代碼分支,執(zhí)行創(chuàng)建守護(hù)進(jìn)程,exec.Command(./go_start -c param1 -d param2 -e param3 -daemon),第二次進(jìn)入main方法
3、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3 -daemon],此時(shí)包含"-daemon",進(jìn)入step3,走創(chuàng)建業(yè)務(wù)進(jìn)程分支,執(zhí)行創(chuàng)建業(yè)務(wù)進(jìn)程,exec.Command(./go_start -c param1 -d param2 -e param3);此時(shí)守護(hù)進(jìn)程存在,每隔5秒監(jiān)聽(tīng)一次業(yè)務(wù)進(jìn)程是否存在,如果存在則不操作;不存在則重新執(zhí)行創(chuàng)建業(yè)務(wù)進(jìn)程exec.Command(./go_start -c param1 -d param2 -e param3);
4、執(zhí)行具體的業(yè)務(wù)進(jìn)程邏輯
驗(yàn)證
ps -ef | grep go_start
]$ 110 1 ./go_start -c param1 -d param2 -c param3 -- ①
]$ 111 1 ./go_start -c param1 -d param2 -c param3 -daemon -- ②
]$ 112 111 ./go_start -business -c param1 -d param2 -c param3 -- ③
剛開(kāi)始會(huì)出現(xiàn)三個(gè)進(jìn)程,假設(shè)進(jìn)程id如上,一會(huì)之后①會(huì)消失,這是正常的,因?yàn)閯傞_(kāi)始的啟動(dòng)就是①,然后只剩下進(jìn)程②和③
]$ 111 1 ./go_start -c param1 -d param2 -c param3 -daemon -- ②
]$ 112 111 ./go_start -business -c param1 -d param2 -c param3 -- ③
驗(yàn)證kill業(yè)務(wù)進(jìn)程:會(huì)啟動(dòng)新的業(yè)務(wù)進(jìn)程,守護(hù)進(jìn)程不變;所以執(zhí)行:kill 112
]$ 111 1 ./go_start -c param1 -d param2 -c param3 -daemon -- ②
]$ 112 111 [go_start] <defunct> -- ③'
]$ 113 111 ./go_start -business -c param1 -d param2 -c param3 -- ③
這里kill 112后,會(huì)出現(xiàn)一個(gè)僵尸進(jìn)程,不影響實(shí)際業(yè)務(wù)進(jìn)程的創(chuàng)建和運(yùn)行,不需要理會(huì);假設(shè)新創(chuàng)建的業(yè)務(wù)進(jìn)程pid為113
驗(yàn)證kill守護(hù)進(jìn)程:整個(gè)程序退出,也就是執(zhí)行ps -ef | grep go_start后,沒(méi)有對(duì)應(yīng)的守護(hù)進(jìn)程和業(yè)務(wù)進(jìn)程,同時(shí)僵尸進(jìn)程也會(huì)消失;得益于以下代碼,進(jìn)程組
command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
綜上
純golang語(yǔ)言形式實(shí)現(xiàn)了守護(hù)進(jìn)程,針對(duì)啟動(dòng)業(yè)務(wù)進(jìn)程,優(yōu)化點(diǎn):可以使用go func(){}()協(xié)程方式啟動(dòng)更優(yōu)雅,這里先不實(shí)施,待后續(xù)有空改進(jìn);
缺點(diǎn):依然要通過(guò)參數(shù)控制守護(hù)進(jìn)程和業(yè)務(wù)進(jìn)程,-daemon -business,期望統(tǒng)一起來(lái),不用參數(shù)控制
放在(3)實(shí)現(xiàn)
附錄
以下是關(guān)于信號(hào)量的一個(gè)記錄,當(dāng)作參考文檔
信號(hào) | 值 | 動(dòng)作 | 說(shuō)明 |
SIGHUP | 1 | Term | 終端控制進(jìn)程結(jié)束(終端連接斷開(kāi)) |
SIGINT | 2 | Term | 用戶發(fā)送INTR字符(Ctrl+C)觸發(fā) |
SIGQUIT | 3 | Core | 用戶發(fā)送QUIT字符(Ctrl+/)觸發(fā) |
SIGILL | 4 | Core | 非法指令(程序錯(cuò)誤、試圖執(zhí)行數(shù)據(jù)段、棧溢出等) |
SIGABRT | 6 | Core | 調(diào)用abort函數(shù)觸發(fā) |
SIGFPE | 8 | Core | 算術(shù)運(yùn)行錯(cuò)誤(浮點(diǎn)運(yùn)算錯(cuò)誤、除數(shù)為零等) |
SIGKILL | 9 | Term | 無(wú)條件結(jié)束程序(不能被捕獲、阻塞或忽略) |
SIGSEGV | 11 | Core | 無(wú)效內(nèi)存引用(試圖訪問(wèn)不屬于自己的內(nèi)存空間、對(duì)只讀內(nèi)存空間進(jìn)行寫(xiě)操作) |
SIGPIPE | 13 | Term | 消息管道損壞(FIFO/Socket通信時(shí),管道未打開(kāi)而進(jìn)行寫(xiě)操作) |
SIGALRM | 14 | Term | 時(shí)鐘定時(shí)信號(hào) |
SIGTERM | 15 | Term | 結(jié)束程序(可以被捕獲、阻塞或忽略) |
SIGUSR1 | 30,10,16 | Term | 用戶保留 |
SIGUSR2 | 31,12,17 | Term | 用戶保留 |
SIGCHLD | 20,17,18 | Ign | 子進(jìn)程結(jié)束(由父進(jìn)程接收) |
SIGCONT | 19,18,25 | Cont | 繼續(xù)執(zhí)行已經(jīng)停止的進(jìn)程(不能被阻塞) |
SIGSTOP | 17,19,23 | Stop | 停止進(jìn)程(不能被捕獲、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止進(jìn)程(可以被捕獲、阻塞或忽略) SIGTTIN 21,21,26 Stop 后臺(tái)程序從終端中讀取數(shù)據(jù)時(shí)觸發(fā) SIGTTOU 22,22,27 Stop 后臺(tái)程序向終端中寫(xiě)數(shù)據(jù)時(shí)觸發(fā) |
到此這篇關(guān)于Golang守護(hù)進(jìn)程用法示例分析的文章就介紹到這了,更多相關(guān)Golang守護(hù)進(jìn)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談golang 中time.After釋放的問(wèn)題
這篇文章主要介紹了淺談golang 中time.After釋放的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05詳解golang中?work與?module?的區(qū)別與聯(lián)系
Go?模塊通常由一個(gè)項(xiàng)目或庫(kù)組成,并包含一組隨后一起發(fā)布的?Go?包,Go?模塊通過(guò)允許用戶將項(xiàng)目代碼放在他們選擇的目錄中并為每個(gè)模塊指定依賴項(xiàng)的版本,解決了原始系統(tǒng)的許多問(wèn)題,本文將給大家介紹一下golang中?work與?module?的區(qū)別與聯(lián)系,需要的朋友可以參考下2023-09-09解決Go中使用seed得到相同隨機(jī)數(shù)的問(wèn)題
這篇文章主要介紹了Go中使用seed得到相同隨機(jī)數(shù)的問(wèn)題,需要的朋友可以參考下2019-10-10