當Shell遇上了NodeJS
此文已由作者堯飄海授權網易雲社區發布。
歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。
摘要
在企業級系統維護和互聯網運維中,Shell腳本的編寫與維護常必不可少, 但是Shell腳本的編寫與調試常占用了開發人員的大部分工作時間,提高Shell腳本的開發效率可以更好的保證項目質量。隨著NodeJs的不斷發展,其輕量級的底層操作、跨平臺及可擴展性等將在系統應用開發和維護中具有更廣闊的前景。
序言
無論在傳統的企業級系統維護還是在互聯網運維中,Shell腳本的編寫與維護常常必不可少,在系統管理員或開發人員工作中占比重比較大的一部分。Shell腳本的嚴格語法格式對於一般的運維人員來說,常常會在一不留神下而抓狂或查找半天才發現是因為多了或少了一個空格或某語包括號不匹配而導致的錯誤,不但大大的浪費了腳本維護人員的工作時間,還可能影響到工程進度甚至項目的發布裏程碑等。當然,對於非純Geek來說,最重要的還是影響心情,特別是對於一些較復雜的腳本需求,更是必須小心謹慎,因此越來越多的開發人員必須借助於Python、Perl、Ruby等相關的腳本語言來實現,但是常由於平臺的特性或者語言的限制,對系統級的命令調用或者異常處理有限制,最終解決起來並不是十分優雅。 NodeJS的出現或許會給這些開發人員帶來一些新的選擇。
NodeJS從誕生起發展非常迅速,社區活動非常活躍,目前擴展模塊達到1500多個,並且每天都有不同的模塊提交。它是構建在JavaScript引擎V8之上的JavaScript環境,它采用基於單線程的異步事件驅動I/O模型,具有非常高的性能,同時能夠支持多種平臺。日前國外的很多大的軟件或互聯網公司如Microsoft,ebay,yahoo等都在使用NodeJS,國內的網易,淘寶,新浪等互聯網企業也有很多分享和成功的線上案例應用。言歸正傳,希望下文的內容能給不熟悉或不喜歡nix平臺Shell腳本開發或WIN平臺下的批處理編寫的工程師帶來一些幫助,為簡單起見,本文采用Nix平臺為示例,WIN平臺的用戶請參考自行修改或與作者聯系。
首先,我承認Shell腳本中系統命令再加上sed,awk等瑞士軍刀在一起工作已經相當強大,如果你想了解NodeJS的強大之處和如何結合Shell產生強大的工作效率,並且還能具有很好的靈活性,那就讓我們繼續旅程吧:
示例
先看一段簡單的采用Shell 腳本執行一段命令得到其執行時間的腳本diffa.sh:
#!/bin/bashSTART=$(date +%s) # prepare things du -m /home > /tmp/output # done END=$(date +%s) DIFF=((END - $START )) echo "化了$DIFF 秒搞定" chmod +x diff.sh sh diff.sh
執行上面的腳本後,結果如下:
花了0 秒搞定
用戶首次執行一般會耗時幾秒,多次執行的結果可能會在0-1秒之間隨機顯示。因為du的輸出重定向,整個腳本的執行時間非常短,並且腳本中采用的是秒數級別的範圍,如果需要得到這個腳本的準確執行時間只能用納秒來進行,並在Shell做除法運算,把腳本修改一下diffb.sh。
START=$(date +%s%N) du -m /home/> /tmp/output END=$(date +%s%N) DIFF=((END - $START)) SUM=`expr $DIFF / 1000000` echo "化了$SUM MS搞定"
執行一下上面的diffb 腳本就可以得到運行的結果了,需要提醒的是上面的腳本中各表達式的格式都是即定的,如果開發人員不小心多了一個空格或少了一空格,都將導致腳本錯誤。下面采用NodeJS來試試看,首上下載與安裝NodeJS環境,過程非常簡單,具體請參考官方網站或直接apt-get 之類的操作。編寫如下diffc.js腳本:
#!/usr/bin/env node var util = require(‘util‘), spawn = require(‘child_process‘).spawn, ls = spawn(‘du‘, [‘-m‘,‘/home/‘]); var start = +new Date(); ls.stdout.on(‘data‘, function (data) { //console.log(‘stdout: ‘ + data); }); ls.stderr.on(‘data‘, function (data) { console.log(‘stderr: ‘ + data); }); ls.on(‘exit‘, function (code) { var end = + new Date(); console.log(end-start); });
註:上面require中引用的都是系統內置模塊,spawn的格式為spawn(command, [args], [options]),其他請參閱官方文檔。
同樣,chmod +x 對腳本賦予執行權限,執行腳本./diff.js,結果如下:
1113
上面示例中顯示的時間直接是毫秒級別,代碼格式沒有嚴格的限制,流程的控制也會更加靈活,特別是在異常情況下,可以根據用戶的需求處理更小的細節。當然,我承認這個示例需求有些詭異,但是做這樣的比較,並不是說要二者一決高下,只是換一種前端攻城師喜歡的方法去實現一些系統運維需求。在這裏NodeJS腳本本身也是依賴於系統Shell的強大基礎之上。
深入一點
以上示例可以看到,在Shell環境中,NodeJS內置模塊實現常用的功能是即方便又靈活,Linux Shell環境中比較強大的功能之一就是支持輸入輸出重定向功能,用符號<和>來表示。0、1和2分別表示標準輸入、標準輸出和標準錯誤信息輸出,用來指定需要重定向的標準輸入或輸出,比如 2>error.log表示將錯誤信息輸出到文件err.log中。類似的,NodeJS中可以直接采用超復雜的命令來搞定,一般對於我們這些非系統管理員有一定的難度,下面引入強大點的模塊procstreams,它可以實現輸出流重定向等功能,首先用戶需要執行npm install procstreams安裝模塊,編寫示例如下wc.js:
#!/usr/bin/env node var p = require(‘procstreams‘); p(‘cat app.log‘).pipe(‘wc -l‘) .data(function(stdout, stderr) { console.log(stdout); });
wc.js腳本代碼是借助於Shell命令實現統計app.log的行數,相當於Shell環境中的cat app.log | wc -l功能,輸出的結果可以再根據需要再進行靈活處理,另外它還支持then、and和or等操作,類似Shell腳本中的;、&& 和||操作。在實現復雜或交互的功能時,甚至可以完全采用交互的方式進行操作輸入。
另外,用戶執行腳本的時候還需要處理復雜一些的參數對應,node-optimist 及 isaacs‘s nopt 之類的模塊可以非常簡單的幫助攻城師實現這樣的功能,如實現根據用戶的輸入的參數執行需要的系統命令,並可以做相關的邏輯處理的opt.js:
#!/usr/bin/env node var util = require(‘util‘), spawn = require(‘child_process‘).spawn; var argv = require(‘optimist‘).argv; var cmd = argv.cmd; var args = argv.args var option = argv.opt var ls = spawn(cmd , [args ,option]); ls.stdout.on(‘data‘, function (data) { if (!data || !!data) console.log(‘ i believe it‘); }); ls.stderr.on(‘data‘, function (data) { console.log(‘It\‘s a miracle!‘); }); ls.on(‘exit‘, function (code) { console.log(‘it.justHappened();‘); });
用戶使用如下對應格式執行代碼:./opt.js --cmd=ls --args=/m --opt=/home,然後只需要在代碼相關處添加對應的邏輯代碼,把註意力放在業務層,采用js的流程控制實現業務邏輯的分離。
實際應用
在企業線上或系統運維中,常需要對一些進程進行監控和報警,以便通知相關系統管理人員,如下Shell腳本agenta.sh實現了對tomcat6進程監控,如果不存在自動重啟。
#!/bin/sh pid=`ps aux| grep "tomcat6" | grep -v grep | sed -n ‘1P‘ | awk ‘{print $2}‘` if [ -z $pid ]; then echo "begin restart,please waiting..." sudo /etc/init.d/tomcat6 restart exit 1 else echo -e "exist ,don‘t need restart" fi
腳本編寫人員在經過一番努力與折騰後,完成了代碼編寫與調試工作,然後需要通過系統的crontab功能添加如0-59/2 * * * * sh agent.sh的定時任務,如果系統管理員把crontab的權限給禁用了,那就需要得到系統管理員的幫助了。下面使用Nodejs來實現同樣的功能,先假設讀者對grep、sed和awk等常用命令的使用有大概了解,代碼如下agentb.js:
var p = require(‘procstreams‘); var exec = require(‘child_process‘).exec; setInterval(function(){ exec("ps aux| grep ‘tomcat6‘ | grep -v grep | sed -n ‘1P‘ | awk ‘{print $2}‘",function(err,output){ if (err) throw err; if (output.length>0) console.log(‘exist,don\‘t need restart‘); else exec(‘sudo /etc/init.d/tomcat6 restart‘,function(err2,out2){}); })},1000 * 60 * 2);
示例代碼中setInterval的函數的作用通過設置一個回調函數和間隔執行時間來實現定時監控。運行代碼後,同樣可以實現進程監控的功能,也許你會說上面的Shell命令還是很多的。因為你覺得直接使用Shell腳本會更簡單,可是如果你經歷過為空格或配置之類的調試過程,或者需求更加復雜時,采用NodeJS會讓你覺得非常輕松。更重要的是,編寫腳本後,在執行腳本時你可以直接通過chrome debug 工具設置斷點與單步調試,或者在chrome 瀏覽器上進行圖形化調試等操作,具體請參考node-inspector。現在,agentb.js代碼中的Shell命令還是太長了太復雜,調試起來也不太方便,使用procstreams做一下簡化,實現代碼agentc.js如下:
var p = require(‘procstreams‘); setInterval(function(){ p("ps aux").pipe(‘grep tomcat6‘).pipe(‘grep -v grep‘).pipe(‘sed -n 1P‘).pipe("awk $2") .data(function(stdout,stderr){ if (stderr) throw stderr; if (stdout.length>0) { console.log(‘exist,don\‘t need restart‘); } else { console.log(‘restart,waiting...‘); p(‘sudo /etc/init.d/tomcat6 restart‘,function(stdout,stderror){ console.log(stdout); }); } }); },1000 * 60 * 2);
agentc代碼中通過pipe操作可以實現對每個步驟的輸入進行詳細的跟蹤與調試,但是腳本中還是需要對系統的很多內置命令有大概的了解,需要對操作系統的相關功能或語法格式比較熟悉,使用起來還是有點不習慣。攻城師都喜歡編程時能控制住自己把握的,或者在使用簡單的命令的情況下,就能實現需要的功能,再次簡化代碼後得到agentd.js
var p = require(‘procstreams‘); var serviceName = ‘tomcat6‘; var interval =1000 * 60 * 2; setInterval(function(){ p("ps aux").pipe(‘grep ‘ + serviceName) .data(function(stdout,stderr) { if (!!stdout && stdout.indexOf(serviceName)==0) { console.log(‘exist,don\‘t need restart‘); } else { console.log(‘restart,waiting...‘); p(‘sudo /etc/init.d/tomcat6 restart‘,function(stdout,stderror){ }); } }); },interval);
在經過這次修改之後,對系統命令的掌握程度要求明顯更低了,題外話,用戶對系統命令了解的越詳細越好,但如果使用簡單即美的指導去實現同樣的需求,何樂而不為。代碼中serviceName 和interval 參數可以通過node-optimist模塊動態給定,這樣就可以實現一份代碼監控多個進程,並且不需要系統管理員的幫助去添加定時任務的操作。當然,希望這樣操作不會影響系統功能或在權限範圍內。
總結
盡管Linux的Shell環境編程非常的強大,但是編寫或調試Shell腳本時常令人抓狂不已,也沒有很好的圖形化調試工具。當然腳本較復雜時,尤其在需求跨平臺時,腳本改動比常較大,日前,開發人員需要根據平臺的不同,準備多套腳本代碼,如tomcat,apache等,如果采用簡單Shell和NodeJS結合編程,或許只需要把平臺相關的命令提取出來,只需較少改動就能實現跨平臺,可以大大提高工作效率與減少浪費攻城師的時間。個人認為,采用二者結合的方式具有以下優點:
1。采用v8引擎,輕量級模塊,較好跨平臺性,較底層的系統操作,在系統監控運維等方面具有明顯優勢,
2。采用事件驅動非阻塞IO模型,無線程上下文切換和鎖操作,, 可利用多核CPU計算,性能較高,
3。開放源代碼,社區活躍,模塊豐富,底層的擴展實現也較方便。
隨著NodeJS不斷發展和成熟,國內外廠商越來越多的成功案例與分享,在企業級和互聯網系統應用開發和維護中具有更廣闊的前景。
參考資料:
http://NodeJS.org
http://www.infoq.com/cn/articles/tyq-NodeJS-event
http://www.catonmat.net/blog/
http://www.cNodeJS.org
免費體驗雲安全(易盾)內容安全、驗證碼等服務
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 Lily-一個埋點管理工具
【推薦】 IOS渠道追蹤方式
當Shell遇上了NodeJS