java 最少使用(lru)置换算法_一篇文章学会如何基于LRU-K算法设计本地缓存实现流量削峰...
專注于Java領(lǐng)域優(yōu)質(zhì)技術(shù)號,歡迎關(guān)注
作者:一個(gè)Java菜鳥
1、背景介紹
1.1、現(xiàn)象
QPS突然增長2倍以上(45w~60w每分鐘) 將產(chǎn)生下面一些問題:
1)響應(yīng)接口響應(yīng)時(shí)長增加了5倍(qps增加了2倍);
2)機(jī)房局域網(wǎng)交換機(jī)帶寬報(bào)警(1kM帶寬使用了900多M);
3)從redis獲取數(shù)據(jù)接口響應(yīng)時(shí)長增加等。
1.2、原因
1)某業(yè)務(wù)線對有限的產(chǎn)品進(jìn)行推廣;
2)在短時(shí)間內(nèi)有大量重復(fù)數(shù)據(jù)查詢請求;
3)短時(shí)間從redis獲取大量數(shù)據(jù)。
1.3、解決方案
大量請求獲取同一份數(shù)據(jù),在本地存儲這些數(shù)據(jù)。
其優(yōu)點(diǎn)如下:
1)直接從內(nèi)存取數(shù)據(jù),降低響應(yīng)時(shí)間;
2)不走redis,減少服務(wù)與redis之間的交互流量;
3)最終實(shí)現(xiàn)流量削峰
2、LRU-K模型設(shè)計(jì)
2.1、LRU算法介紹
Least recently used(LRU,最近最少使用):根據(jù)數(shù)據(jù)的歷史訪問記錄淘汰數(shù)據(jù)。
核心思想
如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率更高。
命中率
當(dāng)存在熱點(diǎn)數(shù)據(jù)時(shí),LRU的效率很好,但偶發(fā)性的、周期性的批量操作會導(dǎo)致LRU命中率急劇下降,緩存污染情況比較嚴(yán)重。
LRU算法模型如下圖:
1)新數(shù)據(jù)插入到鏈表頭部;
2)每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表頭部;
3)當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄。
2.2、LRU-K算法設(shè)計(jì)
LRU-K中的K代表最近使用的次數(shù)。
主要目的
解決LRU算法“緩存污染”的問題。
核心思想
“最近使用過1次”的判斷標(biāo)準(zhǔn)擴(kuò)展為“最近使用過K次”。
命中率
LRU-K降低了“緩存污染”帶來的問題,命中率比LRU要高。
LRU-K模型如下圖:
1)數(shù)據(jù)第一次被訪問,加入到訪問歷史記錄表(簡稱記錄表);在記錄表中對應(yīng)的K單元中設(shè)置最后訪問時(shí)間=new(),且設(shè)置訪問次數(shù)為1;
2)如果數(shù)據(jù)訪問次數(shù)沒有達(dá)到K次,則訪問次數(shù)+1。最后訪問時(shí)間與當(dāng)前時(shí)間間隔超過預(yù)設(shè)的值(如30秒),訪問次數(shù)清0并加1;
3)當(dāng)數(shù)據(jù)訪問計(jì)數(shù)超過(>=)K次后,則訪問次數(shù)+1。將數(shù)據(jù)保存到LRU緩存隊(duì)列中,緩存隊(duì)列重新按照時(shí)間排序;
4)LRU緩存隊(duì)列中數(shù)據(jù)被再次訪問后,重新排序;
5)LRU緩存隊(duì)列需要淘汰數(shù)據(jù)時(shí),淘汰緩存隊(duì)列中排在末尾的數(shù)據(jù),即:淘汰“倒數(shù)第K次訪問離現(xiàn)在最久”的數(shù)據(jù)。
子模塊LRU存儲模型:
類似ConcurrentHashMap,大致由二維數(shù)組+鏈表+訪問隊(duì)列三部分組成
Segment數(shù)組每個(gè)節(jié)點(diǎn)包含訪問隊(duì)列,訪問隊(duì)列模型如下圖:
Segment數(shù)組每個(gè)節(jié)點(diǎn)都包含一個(gè)訪問隊(duì)列,通過這個(gè)隊(duì)列來實(shí)現(xiàn)lru算法;
訪問隊(duì)列是一個(gè)環(huán)狀雙向鏈表,LRU算法由訪問隊(duì)列實(shí)現(xiàn)
3、緩存框架
3.1、系統(tǒng)數(shù)據(jù)存儲組成
數(shù)據(jù)存儲使用DB+本地緩存(LocalCache)+Redis三層結(jié)構(gòu),如下圖:
3.2、數(shù)據(jù)查詢流程
先從本地緩存取,本地緩存沒有從redis取(同時(shí)更新本地緩存),redis沒有從DB取(同時(shí)更新Redis)。具體步驟如下圖:
1)先計(jì)算該數(shù)據(jù)獲取總次數(shù)
2)未達(dá)到K訪問記錄時(shí)直接從redis取數(shù)據(jù)
3)達(dá)到K次訪問記錄時(shí),從本地緩存取,本地緩存不存在時(shí)從redis獲取數(shù)據(jù)(同時(shí)放入本地緩存中)
3.3、數(shù)據(jù)更新流程
刪除緩存數(shù)據(jù)后,會再次從redis獲取并更新緩存
4、調(diào)優(yōu)過程
4.1、參數(shù)動態(tài)配置
配置參數(shù)如果放在Java類或配置文件中,每次調(diào)整都需要重啟服務(wù),執(zhí)行不方便。
配置參數(shù)包括 K次訪問統(tǒng)計(jì)數(shù)據(jù)清0時(shí)長(5分鐘->30秒)、K次訪問閥值參數(shù)、調(diào)優(yōu)日志開關(guān)(調(diào)優(yōu)時(shí)打開,平時(shí)關(guān)閉)、本地緩存最大數(shù)量等等。
動態(tài)調(diào)整+實(shí)時(shí)生效:配置參數(shù)放在可實(shí)時(shí)更新的組件中(如apollo),每次修改后會立即生效。
4.2、性能調(diào)優(yōu)
4.2.1、多機(jī)器本地緩存同步增加原來的業(yè)務(wù)響應(yīng)時(shí)長
優(yōu)化方案:同步緩存操作改成異步
4.2.2、服務(wù)發(fā)布時(shí)接口抖動
優(yōu)化方案:
1)服務(wù)啟動時(shí)執(zhí)行比較耗時(shí)初始化操作:如jdbc初始化,K次統(tǒng)計(jì)結(jié)構(gòu)初始化。
2)模擬核心dubbo接口,提前生成本地機(jī)器碼。
5、實(shí)際效果
5.1、效果(1)
1)優(yōu)化參數(shù)前QPS增長時(shí),響應(yīng)時(shí)間未見明顯變短。如左右第1列
2)參數(shù)調(diào)優(yōu)上線后。QPS明顯增加后,redis請求響應(yīng)時(shí)間增加的情況下,整體響應(yīng)時(shí)間未見變化。見左右最后1列
5.2、效果(2)
QPS增加4倍,響應(yīng)時(shí)間未見變化,跟平時(shí)一樣
demo源代碼見:
https://download.csdn.net/download/love254443233/10672814參考方法:
com.example.liuyaohua.cache.lruk.LrukCacheTest總結(jié)
以上是生活随笔為你收集整理的java 最少使用(lru)置换算法_一篇文章学会如何基于LRU-K算法设计本地缓存实现流量削峰...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 连接redis_Redis 开
- 下一篇: hive 行转列和列转行的方法_Hive