← 返回博客列表
系統設計

可擴展 LRU 快取設計模式 - 物件導向架構與多策略支援 | OAVOService 面試助攻

2026-01-10

問題背景

在高效能系統架構中,快取策略的選擇和實作直接影響系統效能。傳統的單一 LRU 實作往往缺乏擴展性,當業務需求變化時(如需要支援 LFU、FIFO 等策略),往往需要重寫整個快取模組。

OAVOService 面試助攻團隊 在輔導 Google、Amazon、Meta 等大廠系統設計面試中發現,可擴展快取設計是考察候選人物件導向設計能力和架構思維的經典題目。

問題描述

Problem 2 — Design an Extensible LRU Cache

設計一個可擴展的 LRU 快取類別結構並實作。重點考慮擴展性,因為後續需要新增更多淘汰策略,如 LFU(Least Frequently Used)。

核心要求:

架構設計

1. 策略模式 + 工廠模式

from abc import ABC, abstractmethod
from typing import Any, Optional
import threading

class EvictionStrategy(ABC):
    """淘汰策略抽象基底類別"""
    
    @abstractmethod
    def on_access(self, key: Any) -> None:
        """鍵被存取時的回呼"""
        pass
    
    @abstractmethod
    def on_insert(self, key: Any, value: Any) -> None:
        """插入新鍵值對時的回呼"""
        pass
    
    @abstractmethod
    def evict(self) -> Any:
        """回傳需要淘汰的鍵"""
        pass
    
    @abstractmethod
    def remove(self, key: Any) -> None:
        """移除指定鍵"""
        pass
    
    @abstractmethod
    def size(self) -> int:
        """回傳目前大小"""
        pass

class CacheNode:
    """雙向鏈表節點"""
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None
        self.frequency = 1  # 為LFU策略預留
        self.timestamp = 0   # 為其他策略預留

2. LRU 策略實作

import time

class LRUStrategy(EvictionStrategy):
    """LRU淘汰策略實作"""
    
    def __init__(self):
        # 雙向鏈表哨兵節點
        self.head = CacheNode()
        self.tail = CacheNode()
        self.head.next = self.tail
        self.tail.prev = self.head
        self.key_to_node = {}
    
    def _add_to_head(self, node: CacheNode) -> None:
        """新增節點到頭部"""
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node: CacheNode) -> None:
        """移除指定節點"""
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node: CacheNode) -> None:
        """移動節點到頭部"""
        self._remove_node(node)
        self._add_to_head(node)
    
    def on_access(self, key: Any) -> None:
        """LRU存取:移動到頭部"""
        if key in self.key_to_node:
            node = self.key_to_node[key]
            self._move_to_head(node)
    
    def on_insert(self, key: Any, value: Any) -> None:
        """LRU插入:新增到頭部"""
        node = CacheNode(key, value)
        self.key_to_node[key] = node
        self._add_to_head(node)
    
    def evict(self) -> Any:
        """LRU淘汰:移除尾部節點"""
        last_node = self.tail.prev
        if last_node != self.head:
            self._remove_node(last_node)
            del self.key_to_node[last_node.key]
            return last_node.key
        return None
    
    def remove(self, key: Any) -> None:
        """移除指定鍵"""
        if key in self.key_to_node:
            node = self.key_to_node[key]
            self._remove_node(node)
            del self.key_to_node[key]
    
    def size(self) -> int:
        return len(self.key_to_node)
    
    def get_node(self, key: Any) -> Optional[CacheNode]:
        return self.key_to_node.get(key)

3. 核心快取類別

class ExtensibleCache:
    """可擴展快取核心類別"""
    
    def __init__(self, capacity: int, strategy: EvictionStrategy):
        self.capacity = capacity
        self.strategy = strategy
        self._lock = threading.RLock()  # 執行緒安全
    
    def get(self, key: Any) -> Any:
        """取得快取值 O(1)"""
        with self._lock:
            node = self.strategy.get_node(key) if hasattr(self.strategy, 'get_node') else None
            if node:
                self.strategy.on_access(key)
                return node.value
            return None
    
    def put(self, key: Any, value: Any) -> None:
        """設定快取值 O(1)"""
        with self._lock:
            # 檢查是否已存在
            existing_node = self.strategy.get_node(key) if hasattr(self.strategy, 'get_node') else None
            if existing_node:
                existing_node.value = value
                self.strategy.on_access(key)
                return
            
            # 檢查容量限制
            if self.strategy.size() >= self.capacity:
                evicted_key = self.strategy.evict()
                if evicted_key is None:
                    raise RuntimeError("Failed to evict item from full cache")
            
            # 插入新項目
            self.strategy.on_insert(key, value)
    
    def remove(self, key: Any) -> bool:
        """移除指定鍵"""
        with self._lock:
            if hasattr(self.strategy, 'get_node') and self.strategy.get_node(key):
                self.strategy.remove(key)
                return True
            return False
    
    def size(self) -> int:
        """目前快取大小"""
        return self.strategy.size()
    
    def clear(self) -> None:
        """清空快取"""
        with self._lock:
            # 重新初始化策略
            self.strategy = type(self.strategy)()

擴展策略實作

1. LFU 策略擴展

from collections import defaultdict
import heapq

class LFUStrategy(EvictionStrategy):
    """LFU (Least Frequently Used) 策略"""
    
    def __init__(self):
        self.key_to_node = {}
        self.freq_to_keys = defaultdict(set)
        self.min_frequency = 0
        self.key_to_freq = {}
    
    def _update_frequency(self, key: Any) -> None:
        """更新鍵的頻率"""
        freq = self.key_to_freq.get(key, 0)
        
        # 從舊頻率組中移除
        if freq > 0:
            self.freq_to_keys[freq].discard(key)
            if freq == self.min_frequency and not self.freq_to_keys[freq]:
                self.min_frequency += 1
        
        # 新增到新頻率組
        new_freq = freq + 1
        self.key_to_freq[key] = new_freq
        self.freq_to_keys[new_freq].add(key)
    
    def on_access(self, key: Any) -> None:
        if key in self.key_to_node:
            self._update_frequency(key)
    
    def on_insert(self, key: Any, value: Any) -> None:
        node = CacheNode(key, value)
        self.key_to_node[key] = node
        self.key_to_freq[key] = 1
        self.freq_to_keys[1].add(key)
        self.min_frequency = 1
    
    def evict(self) -> Any:
        if not self.key_to_node:
            return None
        
        # 找到最低頻率的鍵
        evict_key = self.freq_to_keys[self.min_frequency].pop()
        del self.key_to_node[evict_key]
        del self.key_to_freq[evict_key]
        return evict_key
    
    def remove(self, key: Any) -> None:
        if key in self.key_to_node:
            freq = self.key_to_freq[key]
            self.freq_to_keys[freq].discard(key)
            del self.key_to_node[key]
            del self.key_to_freq[key]
    
    def size(self) -> int:
        return len(self.key_to_node)
    
    def get_node(self, key: Any) -> Optional[CacheNode]:
        return self.key_to_node.get(key)

2. 快取工廠

class CacheFactory:
    """快取工廠類別"""
    
    @staticmethod
    def create_cache(cache_type: str, capacity: int) -> ExtensibleCache:
        """建立指定類型的快取"""
        strategies = {
            'lru': LRUStrategy,
            'lfu': LFUStrategy,
            'fifo': FIFOStrategy,  # 可繼續擴展
            'random': RandomStrategy,
        }
        
        if cache_type.lower() not in strategies:
            raise ValueError(f"Unsupported cache type: {cache_type}")
        
        strategy = strategies[cache_type.lower()]()
        return ExtensibleCache(capacity, strategy)

# 使用範例
def demo_usage():
    # 建立LRU快取
    lru_cache = CacheFactory.create_cache('lru', 3)
    lru_cache.put('a', 1)
    lru_cache.put('b', 2)
    lru_cache.put('c', 3)
    print(lru_cache.get('a'))  # 1, 'a'被存取
    lru_cache.put('d', 4)      # 淘汰'b'
    print(lru_cache.get('b'))  # None
    
    # 建立LFU快取
    lfu_cache = CacheFactory.create_cache('lfu', 2)
    lfu_cache.put('x', 10)
    lfu_cache.put('y', 20)
    lfu_cache.get('x')  # x頻率增加
    lfu_cache.put('z', 30)  # 淘汰y (頻率最低)

進階功能

1. 過期時間支援

class TTLExtension:
    """TTL (Time To Live) 擴展"""
    
    def __init__(self, base_strategy: EvictionStrategy):
        self.base_strategy = base_strategy
        self.ttl_map = {}  # key -> expiry_time
    
    def set_ttl(self, key: Any, ttl_seconds: int) -> None:
        expiry_time = time.time() + ttl_seconds
        self.ttl_map[key] = expiry_time
    
    def is_expired(self, key: Any) -> bool:
        if key not in self.ttl_map:
            return False
        return time.time() > self.ttl_map[key]
    
    def cleanup_expired(self) -> None:
        current_time = time.time()
        expired_keys = [k for k, v in self.ttl_map.items() if current_time > v]
        for key in expired_keys:
            self.base_strategy.remove(key)
            del self.ttl_map[key]

2. 統計監控

class CacheStatistics:
    """快取統計資訊"""
    
    def __init__(self):
        self.hits = 0
        self.misses = 0
        self.evictions = 0
        self.puts = 0
    
    def record_hit(self):
        self.hits += 1
    
    def record_miss(self):
        self.misses += 1
    
    def record_eviction(self):
        self.evictions += 1
    
    def record_put(self):
        self.puts += 1
    
    @property
    def hit_rate(self) -> float:
        total = self.hits + self.misses
        return self.hits / total if total > 0 else 0.0

OAVOService 面試技巧

設計原則討論

  1. 單一職責 - 策略類別只負責淘汰邏輯
  2. 開閉原則 - 對擴展開放,對修改封閉
  3. 相依反轉 - 相依抽象而非具體實作

常見面試擴展

複雜度分析


OAVOService 專業面試助攻服務 - 深耕系統設計面試多年,為學員提供 Google、Amazon、Meta、Citadel 等頂級公司的即時面試輔助。我們的專家團隊精通各類設計模式和架構原理,確保你在系統設計面試中展現專業水準。

了解我們的 客製化系統設計面試方案 —— 從快取架構到分散式系統,全方位提升你的設計能力和面試表現。

標籤: LRU快取, 系統設計, 設計模式, 物件導向, 架構設計, OAVOService