緩存知識

權限系統(tǒng)是管理類系統(tǒng)中必不可少的一個模塊照雁,一個好的緩存設計更是權限系統(tǒng)的重中之重,今天來聊下如何更好設計權限系統(tǒng)的緩存担锤。
單節(jié)點緩存
權限校驗屬于使用頻率超高的操作被丧,如果每次都去請求db的話,不僅會給db帶來壓力板祝,也會導致用戶響應過慢宫静,造成很不好的用戶體驗,因此把權限相關數(shù)據(jù)放到緩存中是很有必要的,偽代碼如下:
private static final FUNCTION_CACHE_KEY = "function_cache_key";
public List<Function> loadFunctions() {
// 優(yōu)先從緩存中取
List<Function> functions = cacheService.get(FUNCTION_CACHE_KEY);
if(functions != null){
return functions;
}
// 緩存中沒有孤里,從數(shù)據(jù)庫中取伏伯,并放入緩存
functions = functionDao.loadFunctions();
cacheService.put(FUNCTION_CACHE_KEY, functions);
return functions;
}
推薦使用ehcache作為緩存組件,ehcache是一個純Java的進程內(nèi)緩存框架,支持數(shù)據(jù)持久化到磁盤捌袜,并且支持多種緩存策略说搅,對于權限數(shù)據(jù)這種大數(shù)據(jù)量的緩存可以說是非常合適。
集群緩存
ehcache屬于進程級緩存虏等,對集群支持不是很友好弄唧,雖然可以通過一些方案實現(xiàn)分布式緩存,但總感覺沒有直接用memcached或redis來的痛快霍衫,但直接用memcached或redis的話候引,會經(jīng)過一次網(wǎng)絡調(diào)用,而且對于權限緩存這樣內(nèi)存比較大的數(shù)據(jù)慕淡,性能沒有ehcache這種進程級緩存好背伴。那有沒有一直方案可以兼顧ehcache的性能優(yōu)勢和redis的分布式優(yōu)勢呢?
可以通過ehcache和redis共用的方式來解決這個問題峰髓,大致思路是用ehcache做主緩存傻寂,緩存更新通過MQ在集群間進行通信嫡纠,而redis做為二級緩存使用烁兰。
具體方案如下:
更新數(shù)據(jù)
把數(shù)據(jù)同時放入ehcache和redis中,同時通過MQ通知其它節(jié)點更新自身的緩存听皿,更新的數(shù)據(jù)從redis里面拉取
刪除數(shù)據(jù)
刪除ehcache和redis中數(shù)據(jù)徐紧,同時通過MQ通知其它節(jié)點刪除自身的數(shù)據(jù)
其實對于權限緩存静檬,一般情況下更新操作并不頻繁,通過MQ做變更通知并级,redis做二級緩存拂檩,這樣就可以在集群環(huán)境下仍舊使用ehcache的高效存儲了
用時間戳保證級聯(lián)緩存的一致性
在設計緩存的時候,并不是所有的緩存都是從數(shù)據(jù)庫取的嘲碧,有的緩存是從其它緩存從取的稻励,這樣可以減少使用時的計算時間
數(shù)據(jù)庫 --> 緩存a --> 緩存b
有上面的依賴關系可以看出,緩存a發(fā)生變更時愈涩,緩存b如果不重新從緩存a中重新加載望抽,就會造成緩存臟數(shù)據(jù)。
最直觀的方案是刷新a緩存時履婉,同步刷新b緩存煤篙,但從上述依賴關系可以看到,b依賴a毁腿,a并不依賴b辑奈,b緩存對于a應該是不可見的苛茂,所以從邏輯上來說不符合依賴的規(guī)則。
而且上面只是二級關聯(lián)身害,如果是四級草戈,五級的話塌鸯,上層緩存的變更帶動了太多下級緩存的變更,需要耗費很多時間唐片,因此如果能用延遲刷新或許是更好的方案丙猬。
用時間戳或許是個不錯的辦法,上述例子中费韭,可以給緩存a增加一個時間戳茧球,每次a緩存變更,同步更新時間戳星持。獲取b的時候只需要校驗下a的時間戳是否變更抢埋,變更了就重新加載b緩存,否則直接返回b督暂。
偽代碼如下:
// 權限信息緩存key
private static final FUNCTION_CACHE_KEY = "function_cache_key";
// 權限信息緩存時間戳
private static final FUNCTION_TIME_STAMP = "function_time_stamp";
// 權限信息緩存舊的時間戳
private static final FUNCTION_OLD_TIME_STAMP = "function_old_time_stamp";
// 用戶權限信息緩存key
private static final USER_FUNCTION_CACHE_KEY = "uer_function_cache_key";

// 加載所有的權限信息
public List<Function> loadFunctions() {
// 優(yōu)先從緩存中取
List<Function> functions = cacheService.get(FUNCTION_CACHE_KEY);
if(functions != null){
return functions;
}
// 緩存中沒有揪垄,從數(shù)據(jù)庫中取,并放入緩存
functions = functionDao.loadFunctions();
cacheService.put(FUNCTION_CACHE_KEY, functions);
// 同步更新時間戳
String timeStamp = String.valueOf(System.currentTimeMillis());
cacheService.put(FUNCTION_TIME_STAMP, timeStamp);
return functions;
}

// 根據(jù)用戶id加載用戶的權限信息
public List<Function> loadUserFunctions(Long userId) {
List<Function> functions = loadFunctions();
// 加載緩存中用戶權限信息
List<Function> userFunctions = cacheService.get(USER_FUNCTION_CACHE_KEY + userId);
String newTimeStamp= cacheService.get(FUNCTION_TIME_STAMP);
String oldTimeStamp= cacheService.get(FUNCTION_OLD_TIME_STAMP);
// 如果緩存中沒有用戶權限信息逻翁,或者時間戳不相等饥努,重新從權限信息里面加載用戶權限信息
if(userFunctions == null || newTimeStamp != oldTimeStamp){
userFunctions = getUserFunctions(functions, userId);
// 把用戶權限信息放入緩存
cacheService.put(USER_FUNCTION_CACHE_KEY + userId, functions);
// 把當前時間戳放入緩存
cacheService.put(FUNCTION_OLD_TIME_STAMP, newTimeStamp);
return userFunctions;
}
return userFunctions;
}
需要說明的是八回,上述代碼只是作為示例酷愧,真正開發(fā)時用戶的權限信息一般有更好的處理方式,并不一定是上面示例中每個用戶都單獨放一份緩存缠诅。
因為上面緩存只是二級級聯(lián)溶浴,如果級數(shù)更多,同樣可以用時間戳來進行延遲加載
數(shù)據(jù)庫 --> 緩存a --> 緩存b --> 緩存c --> 緩存d
獲取緩存d時管引,可以校驗 緩存a時間戳 + 緩存b時間戳 + 緩存c時間戳士败,abc任何一個時間戳發(fā)生變化,緩存d都需要重新加載汉匙,思路和上面的差不多拱烁,這里就不多贅述了。
guava 的妙用
對于權限校驗中使用頻率高噩翠,但校驗邏輯又不常變化的地方可以再加一層緩存戏自。
例如一般都權限系統(tǒng)都有對外的接口,可以直接匿名訪問伤锚,校驗代碼如下
// ant風格 url 匹配器
private AntPathMatcher matcher = new AntPathMatcher();
// 可以訪問的匿名url集合擅笔,通常采用ant風格,例如 /open/api/**
// 匿名url通常寫在配置文件中,并且在bean初始化時加載到該集合中
private Set<String> anonymousUrlPatterns = new HashSet<String>();

// 判斷url是否能匿名訪問
public boolean couldAnonymous(String url) {
for (String patternUrl : anonymousUrlPatterns) {
if (matcher.match(patternUrl, url)) {
isMatch = true;
break;
}
}
return isMatch;
}
可以看到,每一次url訪問都會校驗猛们,可以通過加一層緩存來優(yōu)化性能
用分布式緩存感覺有點大材小用念脯,ehcache又有點太重量級,ConcurrentHashMap又不支持緩存策略弯淘,思來想去guava貌似是最好的選擇绿店,改造完后的代碼如下:
// ant風格 url 匹配器
private AntPathMatcher matcher = new AntPathMatcher();
// 可以訪問的匿名url集合,通常采用ant風格,例如 /open/api/**
// 匿名url通常寫在配置文件中庐橙,并且在bean初始化時加載到該集合中
private Set<String> anonymousUrlPatterns = new HashSet<String>();

// 匿名url訪問權限緩存
private static Cache<String, Boolean> anonymousUrlCache = CacheBuilder.newBuilder()
.maximumSize(5000)
.initialCapacity(1000)
.expireAfterAccess(1, TimeUnit.DAYS) // 設置cache中的的對象多久沒有被訪問后過期
.build();

// 判斷url 是否能匿名訪問
public boolean couldAnonymous(String url) {
// 先從緩存中取假勿,有的話直接返回
Boolean couldAnonymousAccess = anonymousUrlCache.getIfPresent(url);
if (couldAnonymousAccess != null) {
return couldAnonymousAccess;
}
boolean isMatch = false;
for (String patternUrl : anonymousUrlPatterns) {
if (matcher.match(patternUrl, url)) {
isMatch = true;
break;
}
}
// 匹配結果放入緩存
anonymousUrlCache.put(url, isMatch);
return isMatch;
}
localStorage 緩存
localStorage 是 HTML5支持的新特性,可以把一些數(shù)據(jù)緩存放在客戶端态鳖,減輕服務器的壓力转培,例如可以把菜單數(shù)據(jù)放到客戶端,菜單數(shù)據(jù)是否過期通過時間戳來判斷浆竭,偽代碼如下:
var timestamp = localStorage.getItem("timestamp" + userId);
// 請求后臺獲取菜單接口浸须,帶上時間戳參數(shù) timestamp
// 后臺校驗時間戳是否變更,如果變更邦泄,返回新的菜單數(shù)據(jù)和新的時間戳删窒,否則不需要返回菜單數(shù)據(jù),仍舊返回舊的時間戳即可
// 后臺接口返回數(shù)據(jù)格式 result = {menus:{},timestamp:""}
var newTimestamp = result.timestamp;
// 時間戳變更虎韵,把新的菜單數(shù)據(jù)和新的時間戳 放入 localStorage
if (newTimestamp != timestamp) {
localStorage.setItem("menus" + userId, JSON.stringify(result.menus));
localStorage.setItem("timestamp" + userId, newTimestamp);
}
有人擔心把緩存放在localStorage中如果被修改會造成安全問題易稠,其實這個擔心是沒必要的,因為權限校驗是在服務器端做的包蓝,localStorage中的緩存只做展示使用驶社,因此修改localStorage時沒有任何意義的。
總結
在不同的情況下测萎,上述場景分別用了ehcache,redis,guava,localStorage做緩存亡电,更加說明了沒有最好的技術,只有最適合的技術硅瞧。通過引入時間戳這種版本號的機制份乒,解決了緩存更新問題。最終的目的只有一個腕唧,保證緩存數(shù)據(jù)一致性的同時或辖,把性能做的極致,用戶體驗做到最好枣接。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颂暇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子但惶,更是在濱河造成了極大的恐慌耳鸯,老刑警劉巖湿蛔,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異县爬,居然都是意外死亡阳啥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門财喳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來察迟,“玉大人,你說我怎么就攤上這事纲缓【砭校” “怎么了喊废?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵祝高,是天一觀的道長。 經(jīng)常有香客問我污筷,道長工闺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任瓣蛀,我火速辦了婚禮陆蟆,結果婚禮上,老公的妹妹穿的比我還像新娘惋增。我一直安慰自己叠殷,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布诈皿。 她就那樣靜靜地躺著林束,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稽亏。 梳的紋絲不亂的頭發(fā)上壶冒,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音截歉,去河邊找鬼胖腾。 笑死,一個胖子當著我的面吹牛瘪松,可吹牛的內(nèi)容都是我干的咸作。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼宵睦,長吁一口氣:“原來是場噩夢啊……” “哼记罚!你這毒婦竟也來了?” 一聲冷哼從身側響起状飞,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤毫胜,失蹤者是張志新(化名)和其女友劉穎书斜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酵使,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡荐吉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了口渔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片样屠。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缺脉,靈堂內(nèi)的尸體忽然破棺而出痪欲,到底是詐尸還是另有隱情,我是刑警寧澤攻礼,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布业踢,位于F島的核電站,受9級特大地震影響礁扮,放射性物質(zhì)發(fā)生泄漏知举。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一太伊、第九天 我趴在偏房一處隱蔽的房頂上張望雇锡。 院中可真熱鬧,春花似錦僚焦、人聲如沸锰提。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽立肘。三九已至,卻和暖如春芭概,著一層夾襖步出監(jiān)牢的瞬間赛不,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工罢洲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踢故,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓惹苗,卻偏偏與公主長得像殿较,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桩蓉,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉載:瀏覽器緩存知識小結及應用 閱讀目錄 1. 瀏覽器緩存基本認識 2. 強緩存的原理 3. 強緩存的管理 4. ...
    meng_philip123閱讀 1,086評論 4 18
  • 瀏覽器緩存淋纲,也就是客戶端緩存,既是網(wǎng)頁性能優(yōu)化里面靜態(tài)資源相關優(yōu)化的一大利器院究,也是無數(shù)web開發(fā)人員在工作過程不可...
    Www劉閱讀 565評論 0 1
  • 瀏覽器緩存洽瞬,也就是客戶端緩存本涕,既是網(wǎng)頁性能優(yōu)化里面靜態(tài)資源相關優(yōu)化的一大利器,也是無數(shù)web開發(fā)人員在工作過程不可...
    單純的土豆閱讀 405評論 0 1
  • 對象狀態(tài) Hibernate中對象的狀態(tài): 臨時/瞬時狀態(tài) 持久化狀態(tài) 游離狀態(tài) 學習Hibernate的對象狀態(tài)...
    Java3y閱讀 372評論 0 6
  • 你有自己的Web緩存知識體系嗎? 嘉賓介紹 趙舜東 江湖人稱趙班長为障,曾在武警某部負責指揮自動化的架構和運維工作晦闰,2...
    meng_philip123閱讀 632評論 0 6