1、最近最少使用算法LRU (Least recently used,最近最少使用)
【實現】:最常見的是使用一個鏈表保存緩存數據
1、新數據插入到鏈表頭部;
2、每當緩存命中(即緩存數據被訪問),將數據移動到鏈表頭部;
3、當鏈表滿的時候將鏈表尾部的數據丟棄;
【代價】
命中時需要遍歷鏈表,找到命中的數據塊索引,然後需要將數據移到頭部。
【改變】
基於以上代價,我們將維護的鏈表改為一個雙向鏈表(即每個節點都有個prev和next),另外需要再多維護一個map,將緩存對象的引用放入map中;
1、新數據插入鏈表頭部,並且放入map中
2、每當需要使用緩存時,首先通過key到map中查找,命中緩存後將數據移動到鏈表頭部(這個移動就非常好移動了,只需要把該節點的prev節點的next屬性賦值為該節點的next節點,同時把該節點的next節點的prev屬性賦值為該節點的prev節點,並且將該節點放入鏈表頭部就可以了)。
3、當鏈表滿的時候將鏈表尾部的數據丟棄,並且刪除map中對應的數據。
【結果】
基於以上改變的LRU算法,完全去除了命中緩存需要遍歷鏈表這個缺點,性能得到了大的提升。
2、使用redis緩存數據,保證熱點數據的緩存用法與原理
說一點:只要限制了redis占用的內存,redis會根據自身數據淘汰策略,加載熱數據到內存。
【實現】:
通過redis本身的設置過期時間來實現緩存熱點數據
1、緩存每命中一次,就重新給該數據設置過期時間
2、那麼經常命中的緩存始終不會過期,不會被刪除,而非熱點數據過期時間一到那麼就會被刪除掉,保證了redis中始終存在的是熱點數據。
【原理】
1、原理其實就是Java中延時阻塞隊列DelayQueue的原理
2、當對redis中緩存數據設置過期時間,相當於將緩存數據放入redis中維護的延時阻塞隊列DelayQueue。
3、DelayQueue會對放入的緩存數據根據過期時間進行排序,時間短的在前面,時間長的在隊列後面。
4、會使用一個或者多個線程循環查詢DelayQueue,一旦能從DelayQueue獲取元素了就說明該緩存數據到期了,就可以取出來並且刪除掉了。
5、當有多個線程都同時查詢DelayQueue的時候,只有一個線程能夠爭取到頭元素,其它線程將被阻塞。當頭元素被取走以後,會喚醒所有阻塞線程,線程競爭頭元素,競爭到頭元素的線程會查詢頭元素的剩余delay時間,並且標記頭元素已經被該線程占有,再根據delay時間wait自己,最後獲取頭元素後喚醒其它阻塞線程。