爲什麼 Bash 腳本總是不穩定?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫過很多 Bash 腳本的人都知道,Bash 的坑不是一般的多"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/123989641","title":null},"content":[{"type":"text","text":"^"}]},{"type":"text","text":"。所以大家都認可應該用 Python 而不是 Bash 的一個理由就是 Bash 腳本不可靠,不僅運行不穩定,而且還很難維護。關於 Bash 腳本很難維護,在《"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/247481c8dc6dc4607c1d7515e","title":null},"content":[{"type":"text","text":"關於 Bash 的 10 個常見誤解"}]},{"type":"text","text":"》裏面有提到,其實是很多人對於 Bash 的語法就根本不熟悉。連最常用的 "},{"type":"codeinline","content":[{"type":"text","text":"if"}]},{"type":"text","text":" 表達式的語法都不甚瞭解,更何談什麼進程替代啊,或者來做"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/434199f2ea6ce36b7d04ed578","title":null},"content":[{"type":"text","text":"Bash 腳本的單元測試"}]},{"type":"text","text":"呢?沒有人會認爲使用一個自己不熟悉的語言來做開發維護工作是一件容易的事情。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過呢,就算是我們對 Bash 語法不熟悉,但我們只是來實現一個簡單的功能。Bash 腳本也沒寫幾行,甚至在命令行都一條一條的驗證過,放到 Bash 腳本里面運行起來就是會莫名其妙的出錯。有些在服務器上的出錯根本沒法兒在本地重現,本地測試的好好的,剛部署上去也好好的,一到晚上或者假期就是各種出錯。所以,有人說這是因爲 Bash 這個語言其實不嚴禁,導致其運行不可靠,不穩定,所以得用 Python。但同樣的功能用 Python 實現起來的代碼量比 Bash 多,所以只是爲了個簡單的功能,還是直接寫到 Bash 腳本里面吧,在 Python 腳本里面用 "},{"type":"codeinline","content":[{"type":"text","text":"os.command"}]},{"type":"text","text":" 把很多命令一個一個包裝起來也是挺煩人的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"https://note.cn.odd-e.com/Q_LDtEpxRWeFTWHhgZn6jg?both#%E4%BB%8E%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%BE%8B%E5%AD%90%E5%BC%80%E5%A7%8B","title":"從一個簡單的例子開始"}},{"type":"text","text":"從一個簡單的例子開始"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們就以一個簡單的例子來看一下爲什麼 Bash 腳本會如此不可靠。用 Bash 腳本爲 GNU/Linux 實現一個很常見的、也很簡單的功能,重啓 "},{"type":"link","attrs":{"href":"https://tomcat.apache.org/","title":null},"content":[{"type":"text","text":"Tomcat"}]},{"type":"text","text":" 服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat 是 Java 開發裏面很常見的一個服務器應用,安裝和使用都很簡單。只要系統上已經安裝了對應的 JDK,配置好 "},{"type":"codeinline","content":[{"type":"text","text":"JAVA_HOME"}]},{"type":"text","text":" 環境變量,把 Tomcat 下載回來後解壓就可以使用了。在 Tomcat 的安裝目錄裏面執行 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/startup.sh"}]},{"type":"text","text":" 就可以啓動 Tomcat 服務,運行 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 就可以停止 Tomcat 服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你讀到這裏,請稍微暫停來思考一下:如果由你來實現「重啓 Tomcat」這個腳本,需要考慮哪些情況呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重啓 Tomcat 服務,無非就是先「停止」再「啓動」就可以了。我們在命令行的 Tomcat 目錄下執行了 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/startup.sh"}]},{"type":"text","text":" 後,打開瀏覽器驗證了 Tomcat 已經啓動。然後再執行 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":",再次打開瀏覽器發現無法訪問了。所以要重啓 Tomcat 就是先後執行這兩個命令就可以了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是有一個要考慮的,如果 Tomcat 已經停止了,再次執行命令來停止 Tomcat 會不會出錯呢?我們在命令行下測試了反覆執行 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 之後,發現除了打印出一個無關緊要的可以被忽略的出錯信息後,執行過程並不會出錯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看起來實現重啓 Tomcat 這個功能的 Bash 腳本無非就是要做如下幾步:1、切換到 Tomcat 的安裝目錄;2、停止 Tomcat;3、啓動 Tomcat。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"#!/bin/bash\nexport JAVA_HOME=/opt/jdk1.8.0_262\nexport PATH=\"$JAVA_HOME/bin:$PATH\"\ncd /home/chaifeng/apache-tomcat-8.5.57\n./bin/shutdown.sh\n./bin/startup.sh"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了防止 Bash 腳本沒有獲取到 "},{"type":"codeinline","content":[{"type":"text","text":"JAVA_HOME"}]},{"type":"text","text":" 這個環境變量的設置,我們在腳本里面還額外設置了一下,這又能避免一些奇怪的場景下無法獲得這個變量的問題。看起來這個腳本很完美了,在本地也執行了一下,確實如我們期望的 Tomcat 已經重啓了。用 "},{"type":"codeinline","content":[{"type":"text","text":"ps"}]},{"type":"text","text":" 命令也確認了重啓前後的 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程 ID 也發生了變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是當我們把這個腳本部署到服務器上以後,情況卻不一樣了,大部分時候這個腳本都無法重啓 Tomcat。明明 Tomcat 已經運行,但是服務端口處於無法訪問的狀態,甚至還會出現好幾個 Tomcat 進程。難道是 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 不起作用嗎?當我們登錄到服務器上,用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 殺掉這些 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程後,手動執行命令啓動 Tomcat,驗證沒有問題。然後再手動運行命令停止 Tomcat,發現也沒問題。更詭異的是,不管我們在命令行反覆測試多少次,這兩個啓動和停止命令都正常工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"明明在命令行下測試過都正常工作的命令,只有放到 Bash 腳本里面執行纔有問題。看來 Bash 腳本的執行確實有時候不知何故的會出問題。難道真的是 Bash 是個不嚴禁的語言,運行不穩定嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"https://note.cn.odd-e.com/Q_LDtEpxRWeFTWHhgZn6jg?both#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%9E%E8%BF%99%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-Bash-%E8%84%9A%E6%9C%AC%E9%83%BD%E8%BF%90%E8%A1%8C%E4%B8%8D%E7%A8%B3%E5%AE%9A%EF%BC%9F","title":"爲什麼連這個簡單的-Bash-腳本都運行不穩定?"}},{"type":"text","text":"爲什麼連這個簡單的 Bash 腳本都運行不穩定?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼在命令行下一條一條的執行 Bash 腳本中的命令都沒有問題,但是放到一個腳本中執行卻會出問題呢?這兩種方式下的命令都一樣,除了用 Bash 語言不嚴禁這個理由來解釋外,好像也沒什麼其他原因了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上,有一個很重要的因素很容易被我們所忽略,就是:"},{"type":"text","marks":[{"type":"strong"}],"text":"兩條命令執行之間的時間差"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在命令行上手工執行 Tomcat 的停止命令 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 後,到輸入了啓動命令 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/startup.sh"}]},{"type":"text","text":" 準備按下回車鍵執行之間,已經過去了好幾秒。即使我們是快速複用命令行的歷史命令,也要耗費2、3秒。而放到一個腳本里面執行,前一個命令執行結束到下一個命令開始運行,中間大概只有幾個毫秒的時間差。幾秒與幾毫秒的時間差的區別就是這個簡單的重啓 Tomcat 腳本運行不穩定的根本原因。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當執行了 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 來停止 Tomcat,實際上只是給 Tomcat 服務器發送了一個「停止」的信號,然後命令就退出。但此時,Tomcat 收到了「停止」信號後開始運行停止前的清理任務,包括但不限於等待尚未結束運行的客戶端連接、釋放數據庫資源、網絡資源、清理臨時文件等。所以 Tomcat 還在運行,並沒有立刻停止。這個停止運行前的清理工作的耗費時間依據我們的項目不同可能會差別很大,從零點幾秒到幾十秒,甚至幾分鐘都有可能。所以,這裏是第一個關鍵的地方,"},{"type":"codeinline","content":[{"type":"text","text":"./bin/shutdown.sh"}]},{"type":"text","text":" 命令執行結束後,Tomcat 可能還在後臺進行清理工作,而沒有停止運行!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等我們輸入了 "},{"type":"codeinline","content":[{"type":"text","text":"./bin/startup.sh"}]},{"type":"text","text":" 後,時間已經過去了幾秒,這個時間差已經足夠讓 Tomcat 在大多數的情況下真正停止運行了。所以 Tomcat 就又正常啓動了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"放在腳本中運行,由於兩條命令之間的時間差可能只有幾毫秒,這麼短的時間不足以讓 Tomcat 停止運行。當我們在本地測試運行這個 Bash 腳本時,停止命令和啓動命令都已經先後結束運行後。此時在後臺,前一個老 Tomcat 進程還尚未結束運行,一個新的 Tomcat 進程就已經啓動。但這個新的 Tomcat 可能還需要幾秒鐘來初始化資源,才能夠開始綁定服務端口,這又爲老 Tomcat 進程的停止爭取到寶貴的幾秒時間。當這個新的 Tomcat 結束了資源初始化後開始綁定服務端口的時候,老 Tomcat 可能已經釋放了這個服務端口並停止運行。所以在本地測試時,這個重啓 Tomcat 的 Bash 腳本表現的非常完美。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當部署到服務器上以後,Tomcat 服務可能會因爲資源使用很多而需要更久的時間來釋放資源,僅僅幾秒是不夠的。這就使得當一個新的 Tomcat 結束了資源初始化後,由於老的 Tomcat 服務還沒有結束資源釋放且沒有釋放服務端口,導致新的 Tomcat 會因爲服務端口被佔用而啓動失敗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是爲什麼這麼簡單的一個重啓 Tomcat 的 Bash 腳本會出現運行不穩定的原因。而且也不會因爲我們用 Python 重寫這個 Bash 腳本就可以解決這個問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"https://note.cn.odd-e.com/Q_LDtEpxRWeFTWHhgZn6jg?both#%E8%AE%A9%E8%BF%99%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%BE%8B%E5%AD%90%E5%8F%98%E7%9A%84%E7%A8%B3%E5%AE%9A","title":"讓這個簡單的例子變的穩定"}},{"type":"text","text":"讓這個簡單的例子變的穩定"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然找到了原因,那就好說了,啓動 Tomcat 之前先把現有的 Tomcat 進程殺掉。因爲 Tomcat 本身是一個 Java 應用,所以也就是殺掉 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在的問題是「如何殺掉進程」。有人會說這很簡單啊,用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 就可以殺掉進程。沒錯,這樣的確可以。而且如果大家去搜索「Linux 殺掉進程」,很多的搜索結果都提到要用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 來殺掉進程。爲什麼要使用選項 "},{"type":"codeinline","content":[{"type":"text","text":"-9"}]},{"type":"text","text":" 呢?有人會說,因爲有時候直接用 "},{"type":"codeinline","content":[{"type":"text","text":"kill"}]},{"type":"text","text":" 命令殺不掉進程,反覆執行也殺不掉,用了選項 "},{"type":"codeinline","content":[{"type":"text","text":"-9"}]},{"type":"text","text":" 之後就可以了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,"},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":"是對系統危害最大的命令之一。也有人會說,我一直用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 來殺進程,也從來沒破壞過系統啊。這是典型的倖存者偏差,就跟開車不繫安全帶、騎車不帶頭盔一樣,確實有很多人不繫安全帶不帶頭盔,也沒見過出事兒。但是隻要發生一次,那就是嚴重傷害。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接用 "},{"type":"codeinline","content":[{"type":"text","text":"kill"}]},{"type":"text","text":" 命令去殺一個進程,默認是給進程發送了 "},{"type":"codeinline","content":[{"type":"text","text":"SIGTERM"}]},{"type":"text","text":" 信號,當進程收到這個終止信號後,可能會開始終止前的清理工作。當清理工作結束後,該進程就會自行終止,如果不需要清理就直接結束了。如果清理時間稍微長一些,就會讓我們產生 "},{"type":"codeinline","content":[{"type":"text","text":"kill"}]},{"type":"text","text":" 命令不工作的錯覺。但我們可能只要稍微多等待幾秒,進程就會結束了。而使用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 則類似於一槍爆頭,沒有給進程在自殺前善後工作的機會。如果進程打開了很大的文件,或者正在寫入數據庫。暴力終止進程的執行是很有可能會導致文件或者數據庫的損壞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲一名稱職的司機,我們一定會系安全帶的。作爲一名稱職的運維工程師,一定不會濫用 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 這個命令的。現在擡頭看看你的周圍,有多少不稱職的運維工程師呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然在執行了停止 Tomcat 的命令後,不能立即強行終止 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程,那就需要等待。很顯然,我們不能預計要等待多久。如果固定的等待一個比較長的時間,而 Tomcat 早已經停止,這就會讓別人覺得這個 Bash 腳本的性能太差了。現在我們修改一下這個重啓 Tomcat 的 Bash 腳本:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"#!/bin/bash\nexport JAVA_HOME=/opt/jdk1.8.0_262\nexport PATH=\"$JAVA_HOME/bin:$PATH\"\ncd /home/chaifeng/apache-tomcat-8.5.57\n./bin/shutdown.sh\nfor ((i=0;i<60;i++)); do\n pgrep java || break\n sleep 1\ndone\npgrep java | xargs --no-run-if-empty kill -9\n./bin/startup.sh"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新增了一個等待循環,每秒鐘都檢查一下 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程,如果沒找到就終止循環,最多等待 60 秒。退出循環後,再次查找 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程,這裏用了 GNU xargs 的選項 "},{"type":"codeinline","content":[{"type":"text","text":"-—no-run-if-empty"}]},{"type":"text","text":" 可以避免 "},{"type":"codeinline","content":[{"type":"text","text":"xargs"}]},{"type":"text","text":" 沒有從管道接受了數據而去執行 "},{"type":"codeinline","content":[{"type":"text","text":"kill -9"}]},{"type":"text","text":" 命令,導致無謂的出錯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏,這個簡單的重啓 Tomcat 的 Bash 腳本看上去沒啥問題了,不管是本地還是服務器上都可以正常的穩定的工作了。而且我們應該也要注意到,討論的這個問題不管我們用 Python 還是 Bash 還是其他什麼語言來實現這個功能,都同樣是需要考慮的。要承認,這個造成 Bash 腳本運行不穩定的原因與 Bash 一點兒關係都沒有。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"https://note.cn.odd-e.com/Q_LDtEpxRWeFTWHhgZn6jg?both#%E8%BF%99%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%BE%8B%E5%AD%90%E8%BF%98%E6%B2%A1%E6%9C%89%E7%BB%93%E6%9D%9F","title":"這個簡單的例子還沒有結束"}},{"type":"text","text":"這個簡單的例子還沒有結束"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到這裏,或許有人會疑惑還需要考慮什麼呢?停止 Tomcat 後,如果在 60 秒內 Tomcat 還沒有停止就會強制停止,然後再啓動 Tomcat。完美,沒什麼要考慮的了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在系統上除了 Tomcat 這個 "},{"type":"codeinline","content":[{"type":"text","text":"java"}]},{"type":"text","text":" 進程外,還有其他的 Java 進程,我們怎樣確保這個重啓腳本不會殺掉其他無關的 Java 進程呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 Tomcat 是以某個特定用戶來運行,如何阻止其他用戶無意中執行這個腳本呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果系統上運行了多個 Tomcat 實例,如果確保這個重啓腳本只會重啓特定的 Tomcat 而不是其他的 Tomcat 服務呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何能夠讓不同的 Tomcat 複用同一個重啓腳本,而不用是爲每個 Tomcat 的安裝實例複製多份腳本呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何讓這個腳本自動判斷 Tomcat 版本並選擇對應的 JDK?以避免使用錯誤的 JDK 來啓動 Tomcat 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何讓這個重啓腳本自動判斷當前目錄是否位於一個 Tomcat 安裝目錄中,運行後重啓的就是這個 Tomcat,這樣就避免了每次使用可能需要傳入 Tomcat 目錄作爲參數了。萬一輸入了錯誤的目錄呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何避免使用特權用戶執行這個腳本來重啓 Tomcat?否則不僅會引入額外的安全風險,也會讓因一些文件所有者發生變化而導致非特權用戶以後無法再管理 Tomcat。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何讓每次重新開機後可以自動執行這個腳本來重啓 Tomcat?如果要做開機自動啓動腳本,是不是還要考慮 System V 還是 Systemd ?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲開機的自動啓動腳本時,如何自動判斷 Tomcat 的所屬用戶,並自動切換到該用戶的權限下執行?這樣可以避免無謂的啓動配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 Tomcat 上部署的服務依賴其他的服務,比如 MySQL,如何讓這個腳本自動判斷依賴的服務已經運行?避免 Tomcat 啓動後服務運行後失敗"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何讓重啓腳本判斷 Tomcat 重啓後服務已經成功運行,否則將以某種方式發出通知?這樣就避免了重啓不成功,但是我們不知道。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何避免這個腳本互斥運行?以防止這個腳本在不同的終端上分別同時運行而導致的運行衝突。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何避免遠程登錄到一個服務器上運行了腳本,並確認 Tomcat 運行正常,結果退出登錄後,Tomcat 服務也會終止運行的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等等……等等……"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了寫出一個穩定、可靠、易用的 Tomcat 重啓腳本,我們有很多要考慮的事情。列出了這麼多可能需要考慮的問題,有哪個問題是和 Bash 有關的?很顯然沒有。如果我們用 Python 去開發這樣一個功能,是否也需要考慮這些問題呢?答案是肯定的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結論,Bash 腳本不穩定的原因其實就是我們自己。用 Python 重寫 Bash 腳本後,原先該有的問題還是存在,只是我們不能再怪罪是 Python 這個語言不嚴謹,Python 運行不穩定了。只能乖乖從我們自身去尋找問題,然後慢慢得以解決。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一開始我們就能夠清楚的意識到我們對於一些基礎技能的欠缺,就可以提前避免很多不該有的問題。比如對 Bash 的語法不熟悉,也存在很多的誤解,那我們怎麼能寫好 Bash?缺乏對於操作系統的一些功能理解,比如濫用 "},{"type":"codeinline","content":[{"type":"text","text":"SIGKILL"}]},{"type":"text","text":" 信號,不明白什麼時候該忽略 "},{"type":"codeinline","content":[{"type":"text","text":"SIGHUP"}]},{"type":"text","text":" 信號等等,那我們怎麼能管理好系統?其次,我們缺乏一個系統的運維知識體系,認爲運維工作就是去使用一個又一個的工具,不理解這些工具在運維知識體系中的位置。那我們如何能夠有效的利用這些不同工具的組合來解決問題呢?再者,我們缺乏從全局系統角度去思考問題,解決了一個又冒出另一個。或者就沒明白這些不同的問題之間可能存在一個共同的根因,總是就問題解決問題,那我們要浪費多少時間和人力纔可以維護好一個系統?最後,做好運維工作,提升運維技能不只是運維工程師才需要掌握的。對於開發工程師也有提升自己工作效率,更有效的與運維團隊協作和溝通,無論是對自己、對團隊、對項目都有好處。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要想了解運維工作的知識體系以及技術脈絡請看"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/1fdaca4a1e82d37396fdca76e","title":null},"content":[{"type":"text","text":"DevOps 技術棧"}]},{"type":"text","text":"。再看看你是否也有"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/247481c8dc6dc4607c1d7515e","title":null},"content":[{"type":"text","text":"關於 Bash 的 10 個常見誤解"}]},{"type":"text","text":"?要想寫出可靠穩定的 Bash 腳本,那一定要寫"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/434199f2ea6ce36b7d04ed578","title":null},"content":[{"type":"text","text":"Bash 腳本的單元測試"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章