什麼時候要用到比Redis還要快的快取?怎麼用?
我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第3篇文章,點選檢視活動詳情
大家好,我是日暮與星辰之間,創作不易,如果覺得有用,求關注,求點贊,求評論,求收藏,求轉發,謝謝。
導言
試想一下這麼一個場景,一使用者想要把他看了好長時間的極速版影片積攢的餘額提現,於是他點選了提現按鈕,嘩啦聲一響,他的錢就到銀行卡了。這樣一個對於使用者很簡單的動作但是對於後臺往往牽扯到十幾個服務(公司規模越大、規範性要求越高,整個呼叫鏈路的服務就越多),而你負責了一個交叉驗證的服務,主要負責校驗上游傳遞給你的記賬標識、資金流標識、付款方賬號、收款方賬號是否和最初申請配置的一樣。
為了產品的良好體驗,大老闆要求請求耗時最多1s要讓使用者看到結果,於是各個服務的負責人battle了一圈,給你的這個服務只預留了50ms的時間。你一想,這還不簡單,直接Redis快取走起來。Redis那一套霹靂啪撒一頓輸出,測試環境也沒有一點問題,結果上線後傻眼了,由於網路波動等原因你的服務經常超時,組長責令你儘快解決,再因為你的服務超時導致他被大老闆罵,你的績效就別想了。這種時候,你該怎麼優化呢?
理論
要想做到比Redis還要快,首先要知道Redis為什麼快,最直接的原因就是Redis所有的資料都在記憶體中,從記憶體中取資料庫比從硬碟中取資料要快幾個數量級。
那麼想比Redis還要快,只能在資料傳輸上下功夫,把不同伺服器之間、甚至不同程序之間的資料傳輸都省略掉,直接把資料放在JVM中,寫個ConcurrentMap用於儲存資料,但是既然是快取,肯定還要整一套刪除策略、最大空間限制、重新整理策略等等。自己手擼一套代價太大了,肯定有大公司有類似的場景把這樣的工作已經給做了,而且他還想賺個好名聲,github裡搜一搜,肯定有現成的解決方案。於是今天我們的主角就出場了,Guava Cache.
實踐
首先用一段程式碼整體介紹一下Guava Cache的使用方式,Cache整體分為CacheBuilder和CacheLoader兩個部分,CacheBuilder負責建立快取物件,再建立的時候配置最大容量、過期方式、移除監聽器,CacheLoader負責根據key來載入value。
java
LoadingCache<Key, Config> configs = CacheBuilder.newBuilder()
.maximumSize(5000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Config>() {
@Override
public Graph load(Key key) throws AnyException {
return loadFromRedis(key);
}
});
適用場景
- 凡事都是有代價的,你願意接受佔用記憶體空間的代價來提升速度
- 儲存佔據的資料量不至於太大,太大會導致
Out of Memory
異常 - 同一個Key會被訪問很多次。
CacheLoader
CacheLoader並不是一定要在build的指定,如果你的資料有多種載入方式,可以使用callable的方式。
cache.get(key, new Callable<Value>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
過期策略
過期策略分為大小基準和時間基準,大小基準可以通過CacheBuilder.maximumSize(long)
和CacheBuilder.maximumWeight(long)
.指定,maximumSize(long)
適用於每個值佔用的空間基本上相等或者差異可以忽略不計,只看key的數量,而maximumWeight(long)
則會計算出每個值所佔據的權重,並保證總權重不大於設定值,其中每個值的計算方式可以通過weigher(Weigher)
進行設定。
時間基礎就很好理解,分為expireAfterAccess(long, TimeUnit)
和expireAfterWrite(long, TimeUnit)
,分別是讀多久後失效和寫入快取後多久失效,讀失效事每次讀快取都會為該值續命。
重新整理策略
CacheBuilder.refreshAfterWrite(long, TimeUnit)
方法提供了自動重新整理的能力,需要注意的是,如果沒有重寫reload方法,那麼只有當重新查到該key的時候,才會進行重新整理操作。
總結
丟擲了問題總要給出答案,不然就太監了,那麼導言中的問題我是如何做的呢,我通過guava cache和redis整了一套二級快取,並且在服務啟動時進行了掃表操作,將所有的配置內容都預先放到guava cache中。guava的重新整理時間設定為五分鐘,並重寫了重新整理操作強制進行重新整理,redis的過期時間設定為一天,並且在資料庫內容更新後,刪除對應Redis快取中的值。如此便可以保證,絕大多數情況下都能命中本地中的guava 快取,且最多有5分鐘的資料不一致(業務可以接受)。 凡事必有代價,作為一名後端開發就是要在各種選擇之間進行選擇,選出一條代價可接受、業務能接受的方案。