思路
每個key都有一個附屬key1,附屬key1能夠是key加特定前綴組成,key對應value爲真正的緩存數據,附屬key1對應的value不重要,能夠是隨便一個值,附屬key1的做用主要是維護緩存更新時間並保證只有一個線程到數據源拉取數據更新緩存
附屬key1的過時時間設置爲緩存刷新時間,好比30s,key的過時時間設置 緩存刷新時間 + 數據源修復預期時間(好比2天)
每次請求數據時,使用setnx(將 key 的值設爲 value ,當且僅當 key 不存在)設置附屬key1,返回結果爲1:設置成功,表明附屬key1過時,須要刷新數據,從數據源獲取數據更新緩存,若返回結果爲0:設置失敗,表明附屬key1未過時,不須要刷新數據,從緩存key中獲取數據
因爲redis是單線程,setnx操做至關與互斥鎖,在併發狀況下只有一個線程能獲取到鎖,杜絕了大量併發擊穿緩存請求到數據庫的問題
流程圖redis
代碼演示數據庫
package com.liutf.util; import redis.clients.jedis.Jedis; /** * redis工具 **/ public class ReidsUtil { private static final String HOST = "192.168.11.23"; private static final int PORT = 6379; /** * 附屬key前綴 */ private static final String PREFIX = "prefix:"; /** * 數據源修復預期時間 */ private static final int FIX_TIME = 2 * 26 * 60 * 60; /** * 緩存時間過時時PREKEY_TIME的緩存時間 */ private static final int PREKEY_TIME_COMMON = 30; private static Jedis jedis = null; static { jedis = new Jedis(HOST, PORT); } public static String get(String key) { /** * 組裝設置附屬key */ String prefixKey = PREFIX + key; Long setnxResult = jedis.setnx(prefixKey, "1"); /** * 附屬key過時返回null,從數據源獲取數據 * 附屬key未過時,從key中獲取數據 */ if (setnxResult == 1) { jedis.expire(prefixKey, PREKEY_TIME_COMMON); return null; } else { return jedis.get(key); } } public static boolean set(String key, String value) { /** * 組裝設置附屬key */ String prefixKey = PREFIX + key; jedis.setnx(prefixKey, "1"); jedis.expire(prefixKey, PREKEY_TIME_COMMON); jedis.set(key, value); jedis.expire(key, PREKEY_TIME_COMMON + FIX_TIME); return true; } }
public String get(key) { String value = redis.get(key); if (value == null) { //表明緩存值過時 //設置3min的超時,防止del操做失敗的時候,下次緩存過時一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //表明設置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //這個時候表明同時候的其餘線程已經load db並回設到緩存了,這時候重試獲取緩存值便可 sleep(50); get(key); //重試 } } else { return value; }