1. 收益:java
2. 成本:redis
3. 使用場景:算法
1. 超時刪除數據:也就是設置key的過時時間,好比經過expire命令設置的超時key,該策略數據一致性較低,但維護成本也低數據庫
2. LRU/LFU/FIFO算法剔除:主要是針對當Redis的緩存數據達到設置的最大內存如何處理的策略,該策略數據一致性很低,但維護成本也低後端
3. 主動更新:在開發過程當中經過編寫邏輯代碼,控制數據更新,數據一致性高,但維護成本也高緩存
4. 使用狀況:數據一致性要求低就使用最大內存時數據淘汰策略,若是數據一致性要求高,就將主動更新和超時刪除結合使用,最大內存時數據淘汰策略保底服務器
1. 緩存粒度:以用戶信息爲例,在MySQL中用戶表包含多個字段,經過select語句查詢得到用戶信息時,到底是選擇緩存用戶每一個字段的數據,仍是選擇某幾個重要字段的數據進行緩存,緩存全部字段或部分字段就是指緩存粒度,能夠理解爲緩存粒度就是指緩存對象的數據的完整性併發
2. 緩存粒度控制:運維
1. 緩存穿透:在實際應用中,業務層會先向緩存層發出數據請求,若是過這些請求沒有命中(緩存層沒有請求的數據),那麼就會向關係型數據庫層發出請求,從關係型數據庫中取出數據回寫到緩存層中,並返回給業務層,在下一次請求時就能夠從緩存層返回數據;但若是數據庫中也沒有請求的數據,那麼就會返回業務層空值,在後續業務層的請求同一個數據時,緩存層始終都沒有數據,那麼每次都會向數據庫層請求數據,這樣就形成了緩存穿透。異步
2. 產生緩存穿透的緣由:
3. 解決緩存穿透的方法:
1. 什麼是無底洞問題:一般狀況下,能夠經過增長集羣部署的機器數量來提高性能,可是在2010年,FaceBook發如今部署了3000個節點後發現性能反而降低;也就是說,集羣中有更多的機器不表明有更好的性能,但隨着數據量和併發處理量的提高,又必須提高集羣的機器數量,這就是無底洞問題,這個問題沒有好的解決辦法,只能是經過在細節方面的優化處理來儘可能提升性能,好比優化IO操做、優化Redis集羣中的批量命令執行等
1. 問題描述:若是緩存中部分key集中在一段時間內失效,發生大量的緩存穿透,全部的查詢都落在數據庫上,形成了緩存雪崩。形成這個問題的緣由除了是key失效之外,還多是緩存集羣宕機
2. 解決方案:
設置緩存永遠不過時:
從緩存上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。
從功能上看,把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時
從實戰看,這種方法對於性能很是友好,惟一不足的就是構建緩存時候,其他線程(非構建緩存的線程)可能訪問的是老數據
示例僞代碼
String get(final String key) { V v = redis.get(key); String value = v.getValue(); long timeout = v.getTimeout(); if (v.timeout <= System.currentTimeMillis()) { // 異步更新後臺異常執行 threadPool.execute(new Runnable() { public void run() { String keyMutex = "mutex:" + key; if (redis.setnx(keyMutex, "1")) { redis.expire(keyMutex, 3 * 60); String dbValue = db.get(key); redis.set(key, dbValue); redis.delete(keyMutex); } } }); } return value; }
對應的示例僞代碼
String get(String key) { //首先嚐試從redis(或redis集羣)中獲取key對應的數據 String value = redis.get(key); //若是爲null,則使用redis中的分佈式鎖 if (value == null) { //經過setnx方法建立分佈式鎖 if (redis.setnx(key_mutex, "1")) { // 設置分佈式鎖的過時時間,能夠避免死鎖 redis.expire(key_mutex, 3 * 60) value = db.get(key); //從數據庫中取得數據 redis.set(key, value);//回寫到緩存中 redis.delete(key_mutex);//釋放鎖 } else { //其餘線程休息50毫秒後重試 Thread.sleep(50); get(key); } } }
1. 對於一些設置了過時時間的key,若是這些key可能會在某些時間點被超高併發地訪問,是一種很是「熱點」的數據。這個時候,須要考慮一個問題:緩存被「擊穿」的問題,這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是不少key。緩存在某個時間點過時的時候,剛好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過時通常都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。
2. 解決方案:
對應的示例僞代碼
String get(String key) { //首先嚐試從redis(或redis集羣)中獲取key對應的數據 String value = redis.get(key); //若是爲null,則使用redis中的分佈式鎖 if (value == null) { //經過setnx方法建立分佈式鎖 if (redis.setnx(key_mutex, "1")) { // 設置分佈式鎖的過時時間,能夠避免死鎖 redis.expire(key_mutex, 3 * 60) value = db.get(key); //從數據庫中取得數據 redis.set(key, value);//回寫到緩存中 redis.delete(key_mutex);//釋放鎖 } else { //其餘線程休息50毫秒後重試 Thread.sleep(50); get(key); } } }
"提早"使用互斥鎖(mutex key):在value內部設置1個超時值(timeout1),。當從cache讀取到timeout1發現它已通過期時候,立刻延長timeout1並從新設置到cache。而後再從數據庫加載數據並設置到cache中。
設置緩存永遠不過時:
從緩存上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。
從功能上看,把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時
從實戰看,這種方法對於性能很是友好,惟一不足的就是構建緩存時候,其他線程(非構建緩存的線程)可能訪問的是老數據
示例僞代碼
String get(final String key) { V v = redis.get(key); String value = v.getValue(); long timeout = v.getTimeout(); if (v.timeout <= System.currentTimeMillis()) { // 異步更新後臺異常執行 threadPool.execute(new Runnable() { public void run() { String keyMutex = "mutex:" + key; if (redis.setnx(keyMutex, "1")) { redis.expire(keyMutex, 3 * 60); String dbValue = db.get(key); redis.set(key, dbValue); redis.delete(keyMutex); } } }); } return value; }