Guava Cache 过期回源
緩存的更新有兩種方法:
- 被動更新:先從緩存獲取,沒有則回源獲取,再更新緩存;
- 主動更新:發現數據改變后直接更新緩存(在分布式場景下,不容易實現)
在高并發環境,被動回源是需要注意的。 問題:高并發場景下,大量請求在同一時間回源,大量的請求同一時間穿透到后端,容易引起后端服務崩潰(也容易引起并發問題)。
guava cache解決辦法: guava cache保證單線程回源,對于同一個key,只讓一個請求回源load,其他線程阻塞等待結果。同時,在Guava里可以通過配置expireAfterAccess/expireAfterWrite設定key的過期時間,key過期后就單線程回源加載并放回緩存。
這樣通過Guava Cache簡簡單單就較為安全地實現了緩存的被動更新操作。
但是如果對于同一時間大量不同的key同時過期,造成大量不同的key同時回源,這種怎么解決呢?
guava cache實現類似ConcurrentHashMap,維護segment數組,每個segment獨享一個鎖,ConcurrentHashMap是通過這種機制來實現分段鎖,ConcurrentHashMap默認分了16個segment; guava Cache默認是4個segment,故guava cache的并發級別默認是4個,也就是說默認情況下,即便是大量不同的key同時過期,最多只也有4個線程并發回源,理論上不會給后端造成過大的壓力。
guava refresh和expire刷新機制
- expireAfterAccess: 當緩存項在指定的時間段內沒有被讀或寫就會被回收。
- expireAfterWrite:當緩存項在指定的時間段內沒有更新就會被回收。
- refreshAfterWrite:當緩存項上一次更新操作之后的多久會被刷新。
僅僅使用 expireAfterWrite或者expireAfterAccess就可以實現緩存定時過期,但是頻繁的過期會造成頻繁的單線程回源,然而guava cache回源的時候會獨占一個segment的鎖,對于同一個segment的其他的讀操作 處于loading狀態的則會繼續等待,value expire或者為null的key則會阻塞等待segment的鎖。
expireAfterWrite或者expireAfterAccess的實現在數據回源的時候會讓請求block住,以獲取最新的值。數據實時性保證的較好,但是阻塞住請求對于一些響應要求嚴苛的業務可能是沒辦法接受的。那有沒有解決的辦法呢?
我們且看refreshAfterWrite:
refreshAfterWrite通過定時刷新可以讓緩存項保持可用。緩存項只有在被檢索時才會真正刷新(如果CacheLoader.refresh實現為異步,那么檢索不會被刷新拖慢)。也是保證同一個segment的單線程回源,但是與expireAfterWrite不同的是:其他線程訪問loading狀態的key時,僅僅稍微等一會,沒有等到就返回舊值,整個請求就比較平滑。 與此同時,也引入了一個問題,refreshAfterWrite策略下,如果一個key長期沒有被訪問,就有可能會訪問到很久之前的舊值。例如refreshAfterWrite(5),5s刷新一次,如果1min內,這個key都沒有被訪問,那么1min之后訪問這個key,仍然有可能訪問到舊數據,盡管我們設置了5s刷新一次。(guava cache并沒單獨的線程來處理刷新的邏輯,而是通過讀操作來觸發清理工作)
對于這個問題有沒有這種的辦法呢? guava cache支持我們同時使用expireAfterWrite&refreshAfterWrite,我們既可以通過組合的策略既保證性能,又保證不要讀取到太舊的數據。 比如我們有需求:要求請求必須平滑,而且不能讀到5s之前的舊數據。
我們可以如下設置來滿足需求:
| 1 2 3 4 5 | LoadingCache<String, String> cache = CacheBuilder.newBuilder().refreshAfterWrite(4L, TimeUnit.SECONDS).expireAfterWrite(5L, TimeUnit.SECONDS).build(loader); |
expireAfterWrite(5L, TimeUnit.SECONDS)能保證不會讀到5s之前的舊數據,refreshAfterWrite(4L, TimeUnit.SECONDS)能保證大部分請求都在4s左右被刷新,小部分訪問量較少的在5s的時候通過expireAfterWrite策略重新被回源。
guava后臺異步刷新
refreshAfterWrite的刷新調用的是reload,reload的默認實現是在當前線程里面reload,也會造成一些卡頓,如果希望異步reload,需要重載這個方法。
| 1 2 3 4 5 6 | public ListenableFuture<V> (K key, V oldValue) throws Exception {checkNotNull(key);checkNotNull(oldValue);return Futures.immediateFuture(load(key));//當前線程調用load,會造成當前線程的卡頓,如果不接受卡頓,需要重載這個方法}? |
?轉載:https://www.dazhuanlan.com/2019/10/06/5d99928eb6380/
總結
以上是生活随笔為你收集整理的Guava Cache 过期回源的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电传输之POE供电的介绍
- 下一篇: 外贸网站推广