功能描述: 先從緩存獲取數據,如果緩存沒有,就從數據庫獲取數據,並設置到緩存中,返回數據。
如果數據庫中沒有數據,需要設置一個緩存標記flagKey,防止暴擊訪問數據庫,用緩存保護數據庫。
當刪除緩存key時,需要同時刪除flagKey,保證數據庫可被訪問。
關於java泛型的知識點,這里就跳過了。不理解的就自己去百度一下。
直接上代碼:
1.先定義泛型接口,用於寫從數據庫獲取數據的方法。
/** * 普通數據獲取器 * */ public interface DataGeter<T> { /** * 獲取數據接口 * @return */ public T getData(); }
2.工具類。有一些方法需要自己寫,看注釋。
public class DataUtil { /**json轉換工具*/ private static Gson gson = new Gson(); /**一天的過期秒數*/ private static final int ONE_DAY_EXPIRE_SECONDS = 1 * 24 * 60 * 60; /** * 獲取redis中獲取指定data,如果redis中不存在,則從DataGeter接口中獲取,並且寫入redis<br/> * 注:該方法會自動創建一個cacheKey + "_flag"的緩存key,作為標記flagKey * @param cacheKey-緩存key * @param clazz-獲取目標類型 * @param dataGeter-數據獲取器,原始數據源 * @param expireSeconds-緩存失效時間 * @return */ public static <T> T getDataFromRedisOrDataGeter(String cacheKey, Class<T> clazz, DataGeter<T> dataGeter,int expireSeconds) { T val = RedisClient.getValue(cacheKey, clazz); if (val != null) { //讓熱數據一直熱下去 <單個key/value>,設置緩存有效期。 haveChanceToSetKeyExpireTime( cacheKey, expireSeconds) ; return val; } if (RedisClient.existsKey(RedisClient.getFlagKey(cacheKey))) { return null; } try {
//本地鎖 Object lockObject = LockObjectUtil.getLockObject(cacheKey); synchronized (lockObject) {// 為提升性能 這里不使用分布式鎖 // 標記key,防止集合為空時還不停從數據庫中讀取數據 T data = null; try { data = dataGeter.getData(); } catch (DBException e) { e.printStackTrace(); throw e; } if (data != null) { // 這里最后才設置flag,以免分布式環境下dataGeter.getData();獲取慢,導致后續請求從緩存中取不到數據 //有值不設置flag RedisClient.setValue(cacheKey, data, expireSeconds <= 0 ? RedisDataSource.DEFAULT_EXPIRE_SECONDS : expireSeconds, false); } else { // 不存在數據則往緩存中插入一個flag,過期時間為一天 RedisClient.setValue(RedisClient.getFlagKey(cacheKey), true, ONE_DAY_EXPIRE_SECONDS); } return data; } } finally { //釋放鎖 LockObjectUtil.disposeLock(cacheKey); } } }
當調用工具類的方法時,寫匿名內部類,去實現從數據庫獲取數據的邏輯。