快速失敗機制&失敗安全機制

why技術發表於2020-01-13

這是why技術的第29篇原創文章

之前在寫《這道Java基礎題真的有坑!我求求你,認真思考後再回答。》這篇文章時,我在8.1小節提到了快速失敗和失敗安全機制。

但是我發現當我搜尋"快速失敗"或"失敗安全"的時候,檢索出來的結果百分之90以上都是在說Java集合中是怎麼實現快速失敗或失敗安全的。

在我看來,說到快速失敗、失敗安全時,我們首先想到的應該是這是一種機制、一種思想、一種模式,它屬於系統設計範疇,其次才應該想到它的各種應用場景和具體實現。而不是立馬想到了集合,這樣就有點本末倒置的感覺了。

可以看一下wiki上對於快速失敗和失敗安全的描述:

快速失敗:http://en.wikipedia.org/wiki/Fail-fast

失敗安全:http://en.wikipedia.org/wiki/Fail-safe

簡而言之:系統執行中,如果有錯誤發生,那麼系統立即結束,這種設計就是快速失敗。系統執行中,如果有錯誤發生,系統不會停止執行,它忽略錯誤(但是會有地方記錄下來),繼續執行,這種設計就是失敗安全。

本文就對比一下Java集合中的快速失敗、失敗安全和Dubbo框架中的快速失敗、失敗安全。

讀完之後,你就知道Java集合中實現和Dubbo中的實現就大不一樣。

沒有誰比誰好,只有結合場景而言,誰比誰更合適而已。

Java集合-快速失敗

現象:在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了增加、刪除、修改操作,則會丟擲ConcurrentModificationException。

原理:迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變數。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變數是否為expectedmodCount值,是的話就返回遍歷;否則丟擲ConcurrentModificationException異常,終止遍歷。

注意:這裡異常的丟擲條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設定為了expectedmodCount值,則異常不會丟擲。因此,不能依賴於這個異常是否丟擲而進行併發操作的程式設計,這個異常只建議用於檢測併發修改的bug。

場景:java.util包下的集合類都是快速失敗的,不能在多執行緒下發生併發修改(迭代過程中被修改)。

上面的知識點我在《這道Java基礎題真的有坑!我求求你,認真思考後再回答。》這篇文章中第三小節已經抽絲剝繭般的詳細說明了,有興趣的可以閱讀一下:

Java集合-失敗安全

現象:採用失敗安全機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷。

原理:由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發ConcurrentModificationException。

缺點:基於拷貝內容的優點是避免了ConcurrentModificationException,但同樣地,迭代器並不能訪問到修改後的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。這也就是他的缺點,同時,由於是需要拷貝的,所以比較吃記憶體。

場景:java.util.concurrent包下的容器都是安全失敗,可以在多執行緒下併發使用,併發修改。

比如之前文章說到的CopyOnWriteArrayList:

集合部分涉及到的參考連結:https://www.cnblogs.com/ygj0930/p/6543350.html

Dubbo中的叢集容錯

在描述快速失敗和失敗安全在Dubbo中的體現之前,我們必須先說說Dubbo中的叢集容錯機制,因為快速失敗和失敗安全是其容錯機制中的一種。

還是看之前出現的圖片:

其中,叢集 Cluster 用途是將多個服務提供者合併為一個 Cluster Invoker,並將這個 Invoker 暴露給服務消費者。這樣一來,服務消費者只需通過這個 Invoker 進行遠端呼叫即可,至於具體呼叫哪個服務提供者,以及呼叫失敗後如何處理等問題,現在都交給叢集模組去處理。

叢集模組是服務提供者和服務消費者的中間層,為服務消費者遮蔽了服務提供者的情況,這樣服務消費者就可以專心處理遠端呼叫相關事宜。

對於容錯方式官網上是這樣的說的:

注意哦,官網說的是主要提供了這樣幾種,並沒有完全列舉,通過檢視原始碼你可以看到:

org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker

有九個實現類,說明官方提供了九種容錯方式供你選擇:

而本文主要討論的是:

org.apache.dubbo.rpc.cluster.support.FailfastClusterInvoker org.apache.dubbo.rpc.cluster.support.FailsafeClusterInvoker

搭建Demo專案

為了方便演示快速失敗和失敗安全在Dubbo中的體現,我們需要先搭建一個簡單的Demo專案,搭建過程我就不演示了,這一小節僅對Demo的關鍵地方進行說明:

服務端介面如下:

服務端介面實現如下:

休眠3秒,模擬業務超時。

服務端Dubbo xml配置如下:

消費端Dubbo xml配置如下:

消費端在Test類中消費如下:

Dubbo中的快速失敗

快速失敗對應的實現類是:

org.apache.dubbo.rpc.cluster.support.FailfastClusterInvoker

啟用該實現類,只需要在Dubbo xml中指定cluster屬性為failfast:

先看一下實現類上的註釋是怎麼寫的:

Execute exactly once, which means this policy will throw an exception immediately in case of an invocation error. Usually used for non-idempotent write operations。

FailfastClusterInvoker 只會進行一次呼叫,失敗後立即丟擲異常。適用於冪等操作,比如新增記錄。

實現類的原始碼如下:

執行結果如下:

Dubbo中的失敗安全

失敗安全對應的實現類是:

org.apache.dubbo.rpc.cluster.support.FailsafeClusterInvoker

啟用該實現類,只需要在Dubbo xml中指定cluster屬性為failsafe:

先看一下實現類上的註釋是怎麼寫的:

When invoke fails, log the error message and ignore this error by returning an empty Result. Usually used to write audit logs and other operations

FailsafeClusterInvoker 是一種失敗安全的 Cluster Invoker。所謂的失敗安全是指,當呼叫過程中出現異常時,FailsafeClusterInvoker 僅會列印異常,而不會丟擲異常。適用於寫入審計日誌等操作。

實現類的原始碼如下:

執行效果如下,首先可以看到超時異常被捕獲:

所以雖然超時了,但是在Test類中,還是列印出了returnStr:

文章背後的故事

本週輸出這篇文章實屬不易。

由於週六公司年會與運動會,我有幸當選了某隊隊長,所以本週週一到週五工作之外的時間都在忙碌著運動會的物質籌備、資料統計等相關工作。僅僅有早起的一小會空擋時間能隨手翻翻書,但是寫文章時間是不夠的。

週六活動開始,從早上6點30分起床,到晚上23點做完最後的收尾工作,忙碌了整整一天。所幸的是我所在的小隊在10支隊伍中脫穎而出,勇奪第一。

在戰況異常激烈的拔河環節,在所有拉拉隊員整齊劃一,嘶聲吶喊的口號聲中,參賽隊員體現出的那種堅韌不拔、咬牙死撐的精神,深深的打動了我。一共有兩次輪空的機會,我們隊伍都與之擦肩而過,但是我們一次又一次的擊敗了實力強勁的對手,還是握住了總決賽拔河的繩子。由於對手輪空一輪,所以體力充沛,我們遺憾告負。但是在裁判沒有吹哨之前,我們永不言棄。

所有專案比完之後,我所在隊伍最終的總積分排名第一。在最終結果沒有宣佈之前,我們永不言棄。

週日,和我從小一起長大的表姐結婚,早上7點多去現場幫忙。中午吃飯時我都沒敢喝太多酒,因為我想著這周的文章還沒寫,我得保持清醒。但是看到姐姐和姐夫站在大熒幕下的那一刻,我還是感動的熱淚盈眶。下午陪家人玩了一下午。晚上到家之後已經非常疲倦了,但是我還是輸出了這篇文章。

女朋友問我這周能不能不寫,我想了一下說:不能。因為那一瞬間我想到了,路遙先生在《早晨從中午開始》中的一句話:只有初戀般的熱情和宗教般的意志,人才有可能成就某種事業。

這也和年會中的一頁PPT非常的符合:把你的全部,奉獻給你熱愛的一切。

我是一個普通的程式設計師,但是我熱愛這個行業。

最後說一句

如果把Java集合的實現和Dubbo框架的實現分開來看,感覺這是兩個不同的知識點,但是再往上抽離,可以發現它們都是快速失敗機制與失敗安全機制的實現方式。還是有著千絲萬縷的聯絡。

還是之前說的,快速失敗機制與失敗安全機制,沒有誰比誰好,只有結合場景而言,誰比誰更合適而已。

與本文相關的文章還有下面兩篇,歡迎閱讀:

《這道Java基礎題真的有坑!我求求你,認真思考後再回答。》

《這道Java基礎題真的有坑!我也沒想到還有續集。》

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。

如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。

以上。

歡迎關注公眾號【why 技術】,堅持輸出原創。願你我共同進步。

相關文章