自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis 為何使用近似 LRU 算法淘汰數據,而不是真實 LRU?

數據庫 Redis
今天碼哥帶大家一起搞定 Redis 的 LRU 算法。LRU 算法的全程是 Least Rencently Used,顧名思義就是按照最近最久未使用的算法進行數據淘汰。

在《??Redis 數據緩存滿了怎么辦???》我們知道 Redis 緩存滿了之后能通過淘汰策略刪除數據騰出空間給新數據。

淘汰策略如下所示:

redis內存淘汰

設置過期時間的 key

volatile-ttl、volatile-random、volatile-lru、volatile-lfu 這四種策略淘汰的數據范圍是設置了過期時間的數據。

所有的 key

allkeys-lru、allkeys-random、allkeys-lfu 這三種淘汰策略無論這些鍵值對是否設置了過期時間,當內存不足都會進行淘汰。

這就意味著,即使它的過期時間還沒到,也會被刪除。當然,如果已經過了過期時間,即使沒有被淘汰策略選中,也會被刪除。

volatile-ttl 和 volatile-randon 很簡單,重點在于 volatile-lru 和 volatile-lfu,他們涉及到 LRU 算法 和 LFU 算法。

今天碼哥帶大家一起搞定 Redis 的 LRU 算法…

近似 LRU 算法

什么是 LRU 算法呢?

LRU 算法的全程是 Least Rencently Used,顧名思義就是按照最近最久未使用的算法進行數據淘汰。

核心思想「如果該數據最近被訪問,那么將來被訪問的幾率也更高」。

我們把所有的數據組織成一個鏈表:

  • MRU:表示鏈表的表頭,代表著最近最常被訪問的數據。
  • LRU:表示鏈表的表尾,代表最近最不常使用的數據。

LRU 算法

可以發(fā)現,LRU 更新和插入新數據都發(fā)生在鏈表首,刪除數據都發(fā)生在鏈表尾。

被訪問的數據會被移動到 MRU 端,被訪問的數據之前的數據則相應往后移動一位。

使用單鏈表可以么?

如果選用單鏈表,刪除這個結點,需要 O(n) 遍歷一遍找到前驅結點。所以選用雙向鏈表,在刪除的時候也能 O(1) 完成。

Redis 使用該 LRU 算法管理所有的緩存數據么?

不是的,由于 LRU 算法需要用鏈表管理所有的數據,會造成大量額外的空間消耗。

除此之外,大量的節(jié)點被訪問就會帶來頻繁的鏈表節(jié)點移動操作,從而降低了 Redis 性能。

所以 Redis 對該算法做了簡化,Redis LRU 算法并不是真正的 LRU,Redis 通過對少量的 key 采樣,并淘汰采樣的數據中最久沒被訪問過的 key。

這就意味著 Redis 無法淘汰數據庫最久訪問的數據。

Redis LRU 算法有一個重要的點在于可以更改樣本數量來調整算法的精度,使其近似接近真實的 LRU 算法,同時又避免了內存的消耗,因為每次只需要采樣少量樣本,而不是全部數據。

配置如下:

maxmemory-samples 50

運行原理

大家還記得么,數據結構 redisObject 中有一個 lru 字段, 用于記錄每個數據最近一次被訪問的時間戳。

typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
/* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time).
*/
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;

Redis 在淘汰數據時,第一次隨機選出 N 個數據放到候選集合,將 lru 字段值最小的數據淘汰。

當再次需要淘汰數據時,會重新挑選數據放入第一次創(chuàng)建的候選集合,不過有一個挑選標準:進入該集合的數據的 lru 的值必須小于候選集合中最小的 lru 值。

如果新數據進入候選集合的個數達到了 maxmemory-samples 設定的值,那就把候選集合中 lru 最小的數據淘汰。

這樣就大大減少鏈表節(jié)點數量,同時不用每次訪問數據都移動鏈表節(jié)點,大大提升了性能。

Java 實現 LRU Cahce

LinkedHashMap 實現

完全利用 Java 的LinkedHashMap實現,可以采用組合或者繼承的方式實現,「碼哥」使用組合的形式完成。

public class LRUCache<K, V> {
private Map<K, V> map;
private final int cacheSize;

public LRUCache(int initialCapacity) {
map = new LinkedHashMap<K, V>(initialCapacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize;
}
};
this.cacheSize = initialCapacity;
}
}

重點在于 LinkedHashMap的第三個構造函數上,要把這個構造參數accessOrder設為 true,代表LinkedHashMap內部維持訪問順序。

另外,還需要重寫removeEldestEntry(),這個函數如果返回true,代表把最久未被訪問的節(jié)點移除,從而實現淘汰數據。

自己實現

其中代碼是從 LeetCode 146. LRU Cache 上摘下來的。代碼里面有注釋。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 在鏈頭放最久未被使用的元素,鏈尾放剛剛添加或訪問的元素
*/
class LRUCache {
class Node {
int key, value;
Node pre, next;

Node(int key, int value) {
this.key = key;
this.value = value;
pre = this;
next = this;
}
}
private final int capacity;// LRU Cache的容量
private Node dummy;// dummy節(jié)點是一個冗余節(jié)點,dummy的next是鏈表的第一個節(jié)點,dummy的pre是鏈表的最后一個節(jié)點
private Map<Integer, Node> cache;//保存key-Node對,Node是雙向鏈表節(jié)點

public LRUCache(int capacity) {
this.capacity = capacity;
dummy = new Node(0, 0);
cache = new ConcurrentHashMap<>();
}
public int get(int key) {
Node node = cache.get(key);
if (node == null) return -1;
remove(node);
add(node);
return node.value;
}
public void put(int key, int value) {
Node node = cache.get(key);
if (node == null) {
if (cache.size() >= capacity) {
cache.remove(dummy.next.key);
remove(dummy.next);
}
node = new Node(key, value);
cache.put(key, node);
add(node);
} else {
cache.remove(node.key);
remove(node);
node = new Node(key, value);
cache.put(key, node);
add(node);
}
}
/**
* 在鏈表尾部添加新節(jié)點
*
* @param node 新節(jié)點
*/
private void add(Node node) {
dummy.pre.next = node;
node.pre = dummy.pre;
node.next = dummy;
dummy.pre = node;
}
/**
* 從雙向鏈表中刪除該節(jié)點
*
* @param node 要刪除的節(jié)點
*/
private void remove(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
}
責任編輯:姜華 來源: 碼哥字節(jié)
相關推薦

2020-02-19 19:18:02

緩存查詢速度淘汰算法

2020-05-15 17:05:51

Oracle數據庫LRU算法

2019-12-24 10:32:01

OracleLRU臟塊

2021-07-15 14:29:06

LRU算法

2023-07-06 12:39:14

RedisLRULFU

2020-09-18 10:31:47

LRU算法數組

2022-06-17 07:49:14

緩存LRU

2021-03-01 18:42:02

緩存LRU算法

2021-09-05 18:29:58

Linux內存回收

2015-07-29 10:31:16

Java緩存算法

2017-04-20 09:21:44

pythonLRU算法

2020-10-30 11:30:15

Least Recen

2020-03-06 15:36:01

Redis內存宕機

2020-08-25 17:50:36

Redis數據庫內存

2021-03-10 10:40:04

Redis命令Linux

2022-03-14 08:01:06

LRU算法線程池

2022-05-02 17:34:25

大數據數據分析

2024-03-15 07:17:51

MySQLLRU算法緩存池

2018-01-22 08:33:28

SparkHadoop計算

2009-07-23 11:11:18

LRU緩存
點贊
收藏

51CTO技術棧公眾號