最近產(chǎn)品上有用到一個(gè)叫 supervisord 的開(kāi)源軟件。
https://github.com/ochinchina/supervisord
supervisord 是一個(gè)進(jìn)程管理軟件,golang 寫(xiě)的, 3.5K star。
我們的產(chǎn)品上有多個(gè)服務(wù)程序要在后臺(tái)長(zhǎng)期運(yùn)行,所以使用 supervisord 來(lái)守護(hù)并管理這些進(jìn)程。
supervisord 非常適合嵌入式 Linux 平臺(tái)。
一方面,supervisord 可以幫我們?cè)陂_(kāi)機(jī)時(shí)后臺(tái)啟動(dòng)多個(gè)服務(wù)程序,另一方面,當(dāng)有服務(wù)異常退出時(shí),supervisord 也能幫我們重新將服務(wù)拉起來(lái),從而保證產(chǎn)品能長(zhǎng)期正常工作。
supervisord 的代碼非常值得我們閱讀學(xué)習(xí),不過(guò)今天不研究它。
我們來(lái)研究另外一個(gè)核心功能跟它類(lèi)似,但是代碼更加簡(jiǎn)單的開(kāi)源軟件:pman。
https://github.com/matsune/pman
pman 也是一個(gè)進(jìn)程管理軟件,不過(guò)它是 C++ 寫(xiě)的,代碼僅 1420 行,麻雀雖小,五臟俱全,功能上也完全滿(mǎn)足我們產(chǎn)品的需求。
pman 的用法
舉個(gè)例子:
$ cat /etc/pman.conf
[pman]
pidfile=/tmp/pman.pid ; pman daemon's pidfile
logfile=/tmp/pman.log ; pman daemon's logfile
port=127.0.0.1:50010 ; gRPC server port
directory=/tmp ; default is current dir
[program:ls]
command=/bin/ls ; program command
stdout=/tmp/sample_stdout.log ; program stdout logfile (default: /tmp/${program name}_stdout.log)
stderr=/tmp/sample_stderr.log ; program stderr logfile (default: /tmp/${program name}_stderr.log)
autorestart=true ; automatically restart if exited unexpectedly
autostart=true ; start program on daemon's startup
這個(gè)配置文件指定了一個(gè)需要運(yùn)行的程序:ls,并且指定了它的標(biāo)準(zhǔn)輸出輸出,以及是否要自動(dòng)重啟等屬性。
運(yùn)行效果:
$ ./pman status -c pman.conf
[sleep] RUNNING pid: 15283 uptime: 00:00:04
$ ./pman stop sleep -c pman.conf
[sleep] STOPPING
$ ./pman status -c pman.conf
[sleep] STOPPING
$ ./pman start sleep -c pman.conf
[sleep] RUNNING pid: 15370 uptime: 00:00:00
內(nèi)部實(shí)現(xiàn)
pman 源碼一共才 1420 行,非常適合我們用來(lái)學(xué)習(xí)進(jìn)程管理類(lèi)工具的實(shí)現(xiàn)原理。
$ wc -l *
222 cmd_parser.cpp
45 cmd_parser.hpp
32 conf.cpp
47 conf.hpp
80 conf_parser.cpp
23 conf_parser.hpp
222 daemon.cpp
41 daemon.hpp
29 defines.h
126 main.cpp
56 pid_file.cpp
15 pid_file.hpp
88 pman_client.cpp
21 pman_client.hpp
188 pman_service_impl.cpp
39 pman_service_impl.hpp
35 program.cpp
27 program.hpp
21 task.hpp
54 util.cpp
9 util.hpp
1420 total
簡(jiǎn)單過(guò)一下源碼,抓主干:
int main()
{
[...]
if (cmdParser.command() == DAEMON) {
// 守護(hù)模式,并啟動(dòng) server
return runServer(confParser.pmanConf(), confParser.programConfs());
} else if (cmdParser.command() == KILL) {
return killServer(confParser.pmanConf();
} else {
// client 模式,會(huì)去訪問(wèn) server
return runClient(confParser.pmanConf().port(), cmdParser.command(), cmdParser.program());
}
}
pman 自己本身就是一個(gè)守護(hù)服務(wù),當(dāng)沒(méi)有傳遞 status、stop、start 等參數(shù)時(shí),pman 會(huì)以守護(hù)進(jìn)程的方式運(yùn)行:
int runServer(...)
{
// 將自己編程守護(hù)進(jìn)程
Daemon daemon(pmanConf, programConfs);
daemon.setup();
// 創(chuàng)建 server 線程
thread(rungRPCServer, pmanConf.port(), ref(daemon)).detach();
// 啟動(dòng)配置文件中指定的所有程序
return daemon.runLoop();
}
server 線程會(huì)建立一個(gè) RPCServer,使用到了 Google 的 gRPC 組件:
https://github.com/grpc/grpc
簡(jiǎn)單來(lái)說(shuō),就是為了實(shí)現(xiàn) status/stop/start 等命令以 RPC client 的方式去調(diào)用到 pman 守護(hù)進(jìn)程內(nèi)的函數(shù) API,這里就不詳細(xì)展開(kāi)了。
接著看 runLoop()
int Daemon::runLoop()
{
// 啟動(dòng)所有的用戶(hù)程序
for (auto program = this->programs_.begin(); program != this->programs_.end(); ++program) {
if (program->autostart()) startProgram(*program);
}
// 循環(huán)處理用戶(hù)的命令行 的操作
while (!abrt_status) {
// 取出用戶(hù)指令
while (!tasks_.empty()) {
Task task = tasks_.front();
// Start / Stop 程序
}
}
}
上面的代碼只是 pman 的主邏輯,其他很多功能,例如命令行參數(shù)的解析、配置文件的解析、程序的管理等功能,都被良好地封裝起來(lái)了,所以我們看起來(lái)才會(huì)這么清晰明了。
總結(jié)
對(duì)于嵌入式 Linux 的產(chǎn)品,如果需要長(zhǎng)期守護(hù)住應(yīng)用程序的運(yùn)行,可以考慮部署 supervisord 或者 pman 等進(jìn)程管理工具。
建議優(yōu)先考慮考慮 Go 版本 supervisord,一方面是因?yàn)?Go 應(yīng)用部署非常簡(jiǎn)單,只要拷貝一個(gè)可執(zhí)行文件即可,另一方面也是因?yàn)?supervisord 的用戶(hù)更多、活躍度更高,功能更完善。
如果你開(kāi)發(fā)的產(chǎn)品上沒(méi)有 Go 環(huán)境,性能也有限,類(lèi)似路由器 openwrt 系統(tǒng),那就考慮采用 pman,對(duì)其加以定制以滿(mǎn)足最終的需求。
最后,這兩個(gè)開(kāi)源工具的代碼可讀性都非常的好,很適合用來(lái)鍛煉和提升 Go / C++ 的編程能力,喜歡研究技術(shù)的小伙伴們,可以品讀一下源碼,肯定會(huì)有所收獲。
-
嵌入式
+關(guān)注
關(guān)注
5133文章
19502瀏覽量
314362 -
Linux
+關(guān)注
關(guān)注
87文章
11446瀏覽量
212678
發(fā)布評(píng)論請(qǐng)先 登錄
評(píng)論