# LRU緩存算法怎么用
## 一、什么是LRU緩存算法
LRU(Least Recently Used)即"最近最少使用",是一種常用的緩存淘汰策略。其核心思想是:**當緩存空間不足時,優先淘汰最久未被訪問的數據**。
### 1.1 基本工作原理
- 維護一個有序的數據結構(通常為雙向鏈表+哈希表)
- 新訪問的數據移動到"最近使用"端
- 長時間未被訪問的數據逐漸向"最少使用"端移動
- 當需要淘汰數據時,從"最少使用"端移除
### 1.2 算法特點
| 特點 | 說明 |
|------|------|
| 時間復雜度 | 理想情況下O(1)的讀寫操作 |
| 空間復雜度 | O(n)的額外空間開銷 |
| 適用場景 | 訪問具有局部性特征的場景 |
## 二、LRU算法的典型應用場景
### 2.1 數據庫緩存
MySQL等數據庫的Buffer Pool使用改進版LRU算法管理內存中的頁緩存。
```sql
-- MySQL配置示例
SET GLOBAL innodb_buffer_pool_size = 2147483648; -- 2GB緩沖池
瀏覽器使用LRU管理本地存儲資源: - 最近訪問的網頁資源保留在內存 - 長時間未訪問的資源被淘汰
CDN節點使用LRU管理熱門內容緩存:
# 偽代碼示例
def handle_request(url):
if url in cache:
move_to_front(url)
return cache[url]
else:
data = fetch_from_origin(url)
if cache.full():
evict_last_used()
cache.add(url, data)
return data
Linux內核的頁面緩存機制采用類似LRU的時鐘算法。
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
}
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) return -1;
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
node = new DLinkedNode();
node.key = key;
node.value = value;
cache.put(key, node);
addToHead(node);
if (++size > capacity) {
DLinkedNode tail = removeTail();
cache.remove(tail.key);
--size;
}
} else {
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
class LRUCache extends LinkedHashMap<Integer, Integer> {
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
記錄最近K次訪問記錄,解決”偶發訪問污染”問題。
class LRUKCache:
def __init__(self, capacity, k):
self.history = defaultdict(deque) # 訪問歷史記錄
self.cache = {} # 實際緩存
self.capacity = capacity
self.k = k
動態調整LRU和LFU的比例,IBM研究顯示比LRU提高25%命中率。
// Go語言批量寫入示例
func (c *LRUCache) BatchPut(entries map[int]int) {
c.lock.Lock()
defer c.lock.Unlock()
for key, value := range entries {
if node, exists := c.cache[key]; exists {
c.moveToFront(node)
node.value = value
} else {
node := &Node{key: key, value: value}
c.addToFront(node)
c.cache[key] = node
if len(c.cache) > c.capacity {
deleted := c.removeFromTail()
delete(c.cache, deleted.key)
}
}
}
}
// C++預分配示例
class LRUCache {
private:
struct Node {
int key;
int value;
Node* prev;
Node* next;
};
std::vector<Node> nodePool;
size_t poolIndex = 0;
Node* newNode(int key, int value) {
if (poolIndex >= nodePool.size()) {
nodePool.resize(nodePool.size() * 2);
}
Node* node = &nodePool[poolIndex++];
node->key = key;
node->value = value;
return node;
}
};
// Java分片鎖示例
class ShardedLRUCache {
private final int SHARD_COUNT = 16;
private final LRUCache[] shards;
public ShardedLRUCache(int capacity) {
shards = new LRUCache[SHARD_COUNT];
int perShard = (capacity + SHARD_COUNT - 1) / SHARD_COUNT;
for (int i = 0; i < SHARD_COUNT; i++) {
shards[i] = new LRUCache(perShard);
}
}
private int getShardIndex(int key) {
return key % SHARD_COUNT;
}
public int get(int key) {
return shards[getShardIndex(key)].get(key);
}
}
Redis采用隨機采樣法實現近似LRU:
# Redis配置
maxmemory 1gb
maxmemory-policy allkeys-lru
采樣過程: 1. 維護一個全局LRU時鐘 2. 隨機選取5個key 3. 淘汰最久未使用的key
InnoDB緩沖池使用”young/old”分區設計:
-- 查看相關參數
SHOW VARIABLES LIKE 'innodb_old%';
工作流程: 1. 新頁加入old sublist頭部 2. 再次訪問時移動到young sublist 3. 淘汰總是從old sublist尾部進行
# 啟動參數示例
memcached -m 64 -o modern # 使用現代LRU算法
特性: - 多級LRU隊列(hot/warm/cold) - 惰性淘汰機制 - 動態item遷移
問題類型 | 基礎LRU | 優化方案 | 改進效果 |
---|---|---|---|
緩存污染 | 敏感 | LRU-K | 提升30%+命中率 |
冷啟動 | 嚴重 | 預加載 | 減少50%冷啟動時間 |
掃描操作 | 無抵抗 | 掃描檢測 | 避免90%無效淘汰 |
# Prometheus監控示例
lru_cache_hits_total{cache="user_profile"}
lru_cache_misses_total{cache="user_profile"}
lru_cache_evictions_total{cache="user_profile"}
lru_cache_size_bytes{cache="user_profile"}
# Locust性能測試腳本
from locust import HttpUser, task
class LRUCacheUser(HttpUser):
@task
def test_cache(self):
key = random.randint(1, 10000)
self.client.get(f"/api/data?key={key}")
@task(3)
def test_cache_miss(self):
key = random.randint(10001, 20000)
self.client.get(f"/api/data?key={key}")
“在計算機科學中,所有問題都可以通過增加一個間接層來解決,除了太多間接層導致的問題。” —— David Wheeler
通過合理應用LRU及其變種算法,可以顯著提升系統性能。建議開發者根據實際場景選擇合適的實現方案,并建立完善的監控體系。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。