GuavaCache在高并發場景下的應用

yvck1319 8年前發布 | 82K 次閱讀 并發 Guava 緩存組件

來自: http://neway6655.github.io/cache/2016/02/28/GuavaCache在高并發場景下的應用.html

Guava是Google提供的一套Java工具包,里面內容的含金量非常高,強烈建議深入研究,這次要看的是Cache的部分,Guava Cache提供了一套非常完善的本地緩存機制,在Guava之前,JDK的concurrentHashMap是經常用做本地緩存的,因為能友好的支持并發,但它畢竟還是個Map,不具備緩存的一些特性,比如緩存過期,緩存數據的加載/刷新等。

Guava Cache知識點:

關于GuavaCache的官方使用介紹: https://github.com/google/guava/wiki/CachesExplained

中文翻譯版: http://ifeve.com/google-guava-cachesexplained/

高并發場景下如何使用:

使用緩存,就存在緩存數據一致性的問題,和緩存數據的更新敏感度的問題,這個就是緩存的數據更新問題。

如果是分布式緩存,就另外涉及到分布式的數據一致性問題,這里僅針對本地緩存進行討論。

針對本地緩存,更新方法有很多種,比如最常用的:

  • 被動更新: 是先從緩存獲取,沒有則回源取,再放回緩存;
  • 主動更新: 發現數據改變后直接更新緩存(在多機環境下,一般不會采用)

在高并發場景下,被動更新的回源是要格外小心的,也就是雪崩穿透問題: 如果有太多請求在同一時間回源,后端服務如果無法支撐這么高并發,容易引發后端服務崩潰。

這時Guava Cache上場了,Guava Cache里的CacheLoader在回源的load方法上加了控制,對于同一個key,只讓一個請求回源load,其他線程阻塞等待結果。同時,在Guava里可以通過配置expireAfterAccess/expireAfterWrite設定key的過期時間,key過期后就單線程回源加載并放回緩存。

這樣通過Guava Cache簡簡單單就較為安全地實現了緩存的被動更新操作。

為什么是”較為安全”呢?因為如果同一時間仍有太多的不同key過期,還是會有大量請求穿透緩存而請求到后端服務上,仍然有可能使后端服務崩潰,有什么辦法解決這個問題呢?

1.將key的過期時間加個隨機值,避免大家一起過期(前提是對業務不影響),
2.自己控制回源的并發數,即使有一萬個key要更新,也只讓100個可以回源,其余的9900個等著,(可以通過Guava的Striped實現)
3.在過期前主動更新,更新完成后將過期時間延長
4.大家請繼續大開腦洞想...

另外,如果對剛才說的對于同一個key,只讓一個請求回源,其他線程等待覺得還不爽,雖然對后端服務不會造成壓力,但我的請求都還是blocked了,整個請求還是會被堵一下。

別急,Guava Cache還提供了一個refreshAfterWrite的配置項,定時刷新數據,刷新時仍只有一個線程回源取數據,但其他線程只會稍微等一會,沒等到就返回舊值,整個請求看起來就比較平滑了。為什么又是“比較平滑”呢?因為默認的刷新回源線程是同步的,如果想達到全過程平滑的效果,可以將刷新回源線程做成異步方式。

這樣數據的更新都是在后臺異步做了,但這樣也是有一定的代價的,比如過了刷新時間,仍可能拿到舊值,直到拿回數據更新緩存后才會返回新值。

因為這個refresh動作并不是主動發起的: 比如設置了5秒refresh一下,Guava的做法并不是真的每5秒刷一次,而是等請求到了之后,發現需要refresh時才會真的更新。所以,這一點需要注意,比如雖然設置了5秒刷新,但如果超過1分鐘都沒有請求(假設key沒有過期),當1分零1秒有請求來時,仍有可能返回舊值。(有沒有解決辦法呢?)

以下是關于設置Expire過期和Refresh刷新(sync/async)兩種方式,Guava Cache對請求回源的處理示意圖:

</div>

 本文由用戶 yvck1319 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!