三种缓存模式
最近更新:2025-06-11
|
字数总计:2.1k
|
阅读估时:7分钟
|
阅读量:次
- 旁路缓存(Cache-Aside)
- 描述
- 实际差异
- 设计原因
- 问题
- 读穿透(Read-Through)
- 描述
- 实际差异
- 设计原因
- 问题
- 写穿透(Write-Through)
- 描述
- 实际差异
- 设计原因
- 问题
- 不同模式对比
- 示例代码
旁路缓存(Cache-Aside)
描述
- 应用程序直接管理缓存和数据库的交互。
- 读操作:先查缓存,若缓存命中则返回数据;若未命中,则从数据库查询数据,放入缓存后返回。
- 写操作:直接更新数据库,然后使缓存中的相关数据失效(删除或更新)。
实际差异
- 控制权:应用程序负责缓存的读写逻辑,缓存层(如Redis)仅作为存储介质。
- 灵活性:开发者可以根据业务需求决定何时加载缓存、何时失效。
- 复杂性:应用程序需自行处理缓存一致性问题(如缓存失效、并发更新等)。
- 性能:缓存未命中时,应用程序需要额外查询数据库并更新缓存,增加了请求延迟。
设计原因
- 通用性:适合多种业务场景,因为应用程序可以灵活控制缓存逻辑。
- 解耦性:缓存和数据库是独立的,应用程序可以选择任何缓存技术(如Redis、Memcached)。
- 适用场景:读多写少的场景,如用户信息、文章内容等。避免了缓存层复杂逻辑,保持简单高效。
问题
- 缓存一致性:写操作后需手动失效缓存,可能出现短暂的缓存不一致。
- 缓存穿透:若数据不存在,频繁查询可能直接打到数据库,需额外处理(如布隆过滤器)。
- 冷启动:缓存为空时,所有请求都打到数据库,压力较大。
读穿透(Read-Through)
描述
- 缓存层接管了数据库的查询逻辑。
- 读操作:应用程序直接查询缓存层,若缓存未命中,缓存层自动从数据库加载数据,存入缓存后返回。
- 写操作:通常与Cache-Aside类似,应用程序直接更新数据库并失效缓存,但某些实现中缓存层也可以接管写逻辑。
实际差异
- 控制权:缓存层(如Redis Enterprise、Memcached某些扩展)负责数据加载,应用程序无需关心缓存未命中的逻辑。
- 一致性:缓存层保证数据加载的原子性,减少了应用程序处理复杂逻辑的负担。
- 性能:缓存未命中时,缓存层自动加载数据,减少了应用程序与数据库的直接交互,但缓存层本身的实现可能引入额外开销。
设计原因
- 简化开发:应用程序无需显式处理缓存未命中的情况,降低开发复杂度。
- 一致性保证:缓存层可以实现数据加载的同步机制,避免并发问题。
- 适用场景:适合读密集型场景,缓存层可以优化数据加载逻辑(如批量加载、预热等)。
问题
- 依赖性:需要缓存系统支持Read-Through功能(如某些特定的缓存中间件)。
- 复杂性:缓存层的实现需要考虑数据库连接、加载策略等,增加了系统复杂度。
- 延迟:缓存层与数据库交互可能增加少量延迟。
写穿透(Write-Through)
描述
- 写操作同时更新缓存和数据库。
- 写操作:应用程序写入数据时,缓存层先更新缓存,然后同步更新数据库,确保两者一致。
- 读操作:通常与Read-Through结合,缓存层直接返回数据。
实际差异
- 一致性:缓存和数据库的数据强一致,写操作完成后两者均更新。
- 性能:写操作延迟较高,因为需要同时更新缓存和数据库。
- 复杂性:缓存层需要实现数据库的写逻辑,增加了实现难度。
设计原因
- 强一致性:适合对数据一致性要求高的场景(如金融系统、订单系统)。
- 简化读逻辑:与Read-Through结合时,读操作可以完全依赖缓存,简化应用程序逻辑。
- 适用场景:写操作不频繁但一致性要求高的场景。
问题
- 写性能:每次写操作都需要更新数据库,延迟较高。
- 复杂性:缓存层需要处理数据库写逻辑,可能需要事务支持。
- 资源开销:频繁的写操作会增加数据库负载。
不同模式对比
| 模式 |
读逻辑 |
写逻辑 |
一致性 |
性能 |
适用场景 |
| 旁路缓存 |
应用查缓存,未命中查数据库 |
应用更新数据库,失效缓存 |
最终一致 |
中等 |
通用,读多写少 |
| 读穿透 |
缓存层查数据库 |
通常失效缓存 |
最终一致 |
高(读) |
读密集型,简化开发 |
| 写穿透 |
缓存层查数据库 |
同步更新缓存和数据库 |
强一致 |
低(写) |
一致性要求高 |
| 写回 |
缓存层查数据库 |
先更新缓存,异步写数据库 |
最终一致 |
高(写) |
写密集型,高并发 |
| 写绕 |
缓存层或应用查数据库 |
只更新数据库 |
最终一致 |
高(写) |
写多读少,缓存空间敏感 |
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
private String cacheAside(String productId) { String productInfo = ProductCache.getProductInfo(productId); if (null == productInfo) { productInfo = ProductDao.getProductInfo(productId); ProductCache.putProductInfo(productId, productInfo); } return productInfo; }
private String readThrough(String productId) { return ProductCache.getProductInfoReadThrough(productId); }
private void writeThrough(String productInfo) { String productId = ProductDao.putProductInfo(productInfo); ProductCache.putProductInfo(productId, productInfo); }
public static class ProductCache {
private final static ConcurrentMap<String, String> CACHE = new ConcurrentHashMap<>();
public static void putProductInfo(String productId, String productInfo) { CACHE.computeIfAbsent(productId, (key) -> productInfo); }
public static String getProductInfo(String productId) { System.out.println("--- 正在从数据库加载产品:" + productId + " ---"); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return ProductDao.getProductInfo(productId); }
public static String getProductInfoReadThrough(String productId) { return CACHE.computeIfAbsent(productId, (key) -> ProductDao.getProductInfo(productId)); } }
public static class ProductDao {
private static final Map<String, String> DATABASE = new HashMap<>();
static { DATABASE.put("product:1", "这是产品1的详细信息"); DATABASE.put("product:2", "这是产品2的详细信息"); DATABASE.put("product:3", "这是产品3的详细信息"); }
public static String getProductInfo(String productId) { System.out.println("--- 正在从数据库加载产品:" + productId + " ---"); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return DATABASE.get(productId); }
public static String putProductInfo(String productInfo) { System.out.println("--- 正在往数据库添加产品:" + productInfo + " ---");
String productId; synchronized (DATABASE) { int size = DATABASE.size(); productId = "product:" + size + 1; DATABASE.putIfAbsent(productId, productInfo); } return productId; }
}
|
2025-06-11