你知道強制停機的后果有多嚴重嗎!
有一天,我正在愉快地寫技術文章,結果電腦啪地一下就藍屏了!
哦豁,完蛋,寫了幾千字,忘了保存!
我盲猜很多同學都有這種體驗,可能因為一些突發意外,導致自己的電腦強制停機了,丟失了自己當前的工作。
同樣,對於企業,所有的網站、應用、數據、服務都是掛在服務器上的,一旦意外發生,比如被挖斷了電線、遭遇了自然災害,會導致服務器被強制停機,使得機器上 所有進行中的程序被強制中斷,后果不堪設想!
有同學就笑了,不就是程序被強制中斷么,我們自己偶爾也會用任務管理器或者 kill -9
命令殺個進程啊,抓緊重新啟動程序不就好了,有啥大不了的?
的確,我以前也是通過強殺進程來下線和升級服務的,干脆利落爽。但直到后來有一次,因為強殺進程導致了線上事故,造成了經濟損失和加班,把我嘴都氣歪了!我才意識到自己之前太粗暴、想法太簡單了。
其實,一個程序被強制中斷,除了無法提供服務外,還有很多嚴重的后果!
1. 請求丟失
對於一個 web 服務器,比如 Java Web 開發中主流的 Tomcat。當接受到請求時,會開啟一個線程來處理該請求。而如果請求數較多,線程處理不過來,就會將此請求放入等待隊列中,排隊等待空閑線程。
假設 web 服務進程突然中斷,會導致所有在內存隊列中等待執行的請求丟失,等了半天,等了個空!
2. 業務中斷
一旦進程中斷,會導致 所有 正在執行的業務中斷,會導致很多意想不到的后果。
比如有一個檢查數據的任務,要檢查所有數據庫中狀態為 0 的數據是否正確,代碼流程如下:
// 開始檢查,數據狀態由 0 置為 1
startCheck();
// 檢查
doCheck();
// 結束檢查,將正確的數據狀態置為 2
endCheck();
假設剛把數據的狀態置為 1,表示正在檢查中。然后程序就中斷了,會導致以后這條數據的狀態始終為 1,再也不會被檢查。
同理,如果已經檢查完,並且數據正確,本來應該將數據狀態置為 2,但這時程序中斷,也會導致 數據的狀態和預期不一致。
以上只是一個簡單的例子,但實際的業務場景中,業務中斷可能直接影響收益,尤其是涉及交易的支付轉賬業務,如果用戶已經付款,卻因為程序的中斷,沒有存儲付款記錄,那這個支付業務不是真要涼涼?
3. 事務中斷
數據庫事務是指對數據庫的一系列 不可分割 的操作,具有一致性,每次執行必須使數據庫從一個一致性狀態變到另一個一致性狀態。
比如轉賬業務中,用戶 A 要給用戶 B 轉賬 1 元,用戶 A 扣除 1 元,用戶 B 就要增加 1 元。
但如果用戶 A 已扣除 1元后,應用程序或者數據庫系統突然掛了,導致事務尚未完成就被迫中斷,結果用戶 B 的總金額並沒有變化。這時數據庫就處於不一致狀態。同理,即使在程序中設計了回滾,回滾過程也可能會被中斷!
除了數據不一致外,事務中斷還可能導致鎖行、鎖表,使得這部分 數據的可用性受到影響。
4. 文件損壞
假設程序正在向一個文件進行寫操作,還未完成,就被中斷了,可能會導致文件的不完整、甚至損壞。
這讓我想起小時候,電腦配置不高,有時玩游戲會卡住,然后我就強制殺了進程,結果導致游戲文件損壞,只能重新下載游戲。
5. 任務丟失
我們在編寫業務代碼時,經常會將比較耗時的任務異步化,將任務提交到線程池后立即返回成功。線程池會從任務隊列中依次讀取並執行任務。
而一旦程序中斷,線程池中的任務就會丟失,好像他從來沒有被提交過一樣。這種感覺就像你答應別人要做一件事,別人對你很放心,但你最后卻放了鴿子跑路了。
6. 數據丟失
有時,我們會先將數據臨時放在內存中,然后定期、定時、或者分批地持久化到數據庫或本地磁盤中。
比如 Redis 數據庫的 RDB 機制,每隔一段時間,會將內存中的數據進行本地備份,從而降低大量數據並發寫入時的負載,提升性能。
但就像上面提到的任務丟失一樣,一旦程序中斷,可能會導致很多 未持久化的數據丟失,比如緩存、分批提交數據等。
7. 消息丟失
在分布式系統中,各個節點間經常通過消息來進行交互和協作,而程序的中斷可能會在不同情況下導致消息丟失。
1. 消息未發出
假設某支付業務中,已經扣除了用戶的賬戶余額,並更新了數據庫,接下來要向客戶端返回應答消息。
但是消息正在發送隊列中排隊等待發送時,由於進程被強制退出導致消息未發出,從而導致應答消息丟失。客戶端久久接收不到消息后,可能會發起重試,導致重復更新。
2. 消息未確認
比如說某段業務代碼從消息隊列中取出了一個消息,進行消費處理,代碼流程如下:
// 獲取下一個消息
Message msg = getNextMsg();
// 處理消息
int res = handleMsg(msg);
// 處理成功?
if(res == 0) {
// 確認消息
ack();
} else {
// 拒絕確認消息
nack();
}
無論消息處理成功與否,都必須要給消息隊列一個回復!如果處理成功,要告訴他這條消息已經被我處理完成啦,請給我下一條消息;即使處理失敗,也要告訴消息隊列,請給我重發本條消息。
而一旦程序中斷,這條消息的處理結果便無人知曉,可能導致消息隊列的 阻塞或者無限重發(根據具體消息隊列來決定)。
8. 資源占用
程序的強制中斷可能會導致很多資源的占用未被釋放。比如:
- 空間占用:如已分配的內存未回收,臨時文件未被刪除等。
- 端口占用:會導致這個端口無法被其他應用程序使用。很多同學在本地調試時,應該也會遇到因為強退導致的 3000、8080 端口未被釋放的問題。
- 連接占用:比如和遠程的服務建立了 Http 連接,由於連接未被釋放,會浪費一個連接數,就像買了電影票卻不去一樣。
9. 服務未下線
在微服務場景下,服務通常由集中的注冊中心進行統一的服務發現和管理。
比如 Eureka 注冊中心,服務生產者向注冊中心注冊服務,服務消費者從注冊中心獲取服務地址,然后遠程調用:
而一旦某個服務進程還沒有即時通知注冊中心它要下線,就中斷了,會導致服務消費者仍能獲取到該服務的路由,從而調用失敗。
此外,服務下線時如果未向上游(該服務調用方)通知,還可能導致上游的持續調用,嚴重時會產生雪崩效應,整條服務鏈路中斷!
尤其是在分布式場景下,出現進程強制中斷對集群的影響(比如數據一致性)非常大。正如 FLP不可能定理 的描述:在異步通信場景,即使只有一個進程失敗,也沒有任何算法能保證非失敗進程達到一致性。
其實,相比起這些問題,更可怕的是,如果沒有完善的數據監控和檢測機制,你甚至完全不知道在強制停機后有沒有出現問題?出現了哪些問題?哪些數據丟失?哪些數據不一致?哪些任務需要補償?看不見的危險才最可怕啊!
因此,預防大於治療。一方面要養成良好習慣,無論是對自己的電腦還是服務器,都千萬不要再主動強制停機了;另一方面,也要在程序設計時,做好應對意外停機的防控措施。不要等到失去了,才追悔莫及。