面试题答案
一键面试方案设计
- 分层缓存设计:将缓存分为两层,第一层采用FIFO策略,第二层采用LRU策略。
- 第一层FIFO缓存:作为快速缓存层,容量相对较小,用于快速处理高频访问的数据。当有新数据进入时,按照FIFO策略淘汰最早进入的数据。
- 第二层LRU缓存:容量相对较大,用于存储第一层淘汰下来的数据。当第二层缓存空间不足时,按照LRU策略淘汰最近最少使用的数据。
- 数据读取流程:
- 当请求商品详情页数据时,首先在第一层FIFO缓存中查找。如果找到数据,则直接返回,并将该数据标记为热点数据(不移动到第二层缓存)。
- 如果在第一层FIFO缓存中未找到,则在第二层LRU缓存中查找。若找到,将数据返回,并将该数据移至第一层FIFO缓存(若第一层已满,按照FIFO淘汰一个数据)。
- 数据写入流程:
- 新数据首先写入第一层FIFO缓存。若第一层已满,按照FIFO策略淘汰数据,被淘汰的数据写入第二层LRU缓存。若第二层也已满,按照LRU策略淘汰数据。
方案优势
- 减少缓存抖动:FIFO缓存快速处理高频访问数据,减少不必要的数据淘汰。LRU缓存作为补充,合理管理低频但近期使用过的数据,避免因简单FIFO策略导致的频繁淘汰热点数据,从而减少缓存抖动。
- 提高缓存命中率:分层缓存和数据迁移机制使得热点数据能更高效地存储在靠近访问端的第一层缓存中,提高了整体的缓存命中率,进而提升系统性能。
- 适应性强:结合两种策略可以更好地适应电商系统中数据访问模式的变化,既能处理突发的高频访问,又能保留一定周期内可能再次访问的数据。
实现思路
- 数据结构选择:
- 第一层FIFO缓存:可以使用队列(Queue)数据结构实现,新数据入队,淘汰数据出队。
- 第二层LRU缓存:可以使用哈希表(HashMap)和双向链表(Doubly Linked List)结合的方式实现。哈希表用于快速定位数据,双向链表用于记录数据的使用顺序,便于实现LRU淘汰策略。
- 代码实现:以Java为例:
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class HybridCache {
private int firstCacheSize;
private int secondCacheSize;
private LinkedList<String> firstCacheQueue;
private Map<String, String> firstCacheMap;
private Map<String, Node> secondCacheMap;
private Node head;
private Node tail;
private static class Node {
String key;
String value;
Node prev;
Node next;
Node(String key, String value) {
this.key = key;
this.value = value;
}
}
public HybridCache(int firstCacheSize, int secondCacheSize) {
this.firstCacheSize = firstCacheSize;
this.secondCacheSize = secondCacheSize;
this.firstCacheQueue = new LinkedList<>();
this.firstCacheMap = new HashMap<>();
this.secondCacheMap = new HashMap<>();
this.head = null;
this.tail = null;
}
public String get(String key) {
if (firstCacheMap.containsKey(key)) {
return firstCacheMap.get(key);
} else if (secondCacheMap.containsKey(key)) {
Node node = secondCacheMap.get(key);
moveToHead(node);
addToFirstCache(key, node.value);
return node.value;
}
return null;
}
public void put(String key, String value) {
if (firstCacheMap.containsKey(key)) {
firstCacheMap.put(key, value);
return;
}
if (firstCacheQueue.size() >= firstCacheSize) {
String removedKey = firstCacheQueue.poll();
firstCacheMap.remove(removedKey);
addToSecondCache(removedKey, value);
}
firstCacheQueue.add(key);
firstCacheMap.put(key, value);
}
private void addToSecondCache(String key, String value) {
if (secondCacheMap.containsKey(key)) {
Node node = secondCacheMap.get(key);
node.value = value;
moveToHead(node);
} else {
Node newNode = new Node(key, value);
secondCacheMap.put(key, newNode);
addToHead(newNode);
if (secondCacheMap.size() > secondCacheSize) {
removeTail();
}
}
}
private void addToFirstCache(String key, String value) {
if (firstCacheQueue.size() >= firstCacheSize) {
String removedKey = firstCacheQueue.poll();
firstCacheMap.remove(removedKey);
}
firstCacheQueue.add(key);
firstCacheMap.put(key, value);
}
private void moveToHead(Node node) {
if (node == head) return;
if (node == tail) {
tail = node.prev;
tail.next = null;
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
addToHead(node);
}
private void addToHead(Node node) {
if (head == null) {
head = node;
tail = node;
} else {
node.next = head;
head.prev = node;
head = node;
}
}
private void removeTail() {
if (tail == null) return;
secondCacheMap.remove(tail.key);
if (tail == head) {
head = null;
tail = null;
} else {
tail = tail.prev;
tail.next = null;
}
}
}
上述代码实现了一个简单的混合缓存示例,结合了FIFO和LRU策略,在实际应用中,可根据电商系统的具体需求和数据特点进一步优化和扩展。