一.什么是缓存:
缓存,简单说就是为了节约对原始资源重复获取的开销,而将结果数据副本存放起来以供获取的方式。
二.什么时候使用缓存
1.以 幂等和安全的方式对资源的获取操作
2.缓存数据必须是重复获取的:
缓存能生效的本质是空间换时间
缓存的命中率: 把一批数据获取中,通过缓存获得数据的次数,除以总的次数,得到的结果,叫做缓存的命中率。
3. 缓存是为了解决“开销”的问题,开销包括:时间的开销、CPU、网络、I/O等资源的开销。
4. 缓存的存取其实不一定是“更快”的
5.缓存使用最常用的动机:(1) 一个是 latency,延迟(2) 另一个使用动机,是 throughput,吞吐量
三.缓存应用模式:
1. Cache-Aside(最常用的缓存模式)
(1)获取数据:
应用先去查看缓存是否有所需数据;
如果有,应用直接将缓存数据返回给请求方;
如果没有,应用执行原始逻辑,例如查询数据库得到结果数据;
应用将结果数据写入缓存。
(2)更新数据:
关键点一:必须先更新数据库-再令缓存失效:
(原因在于,如果先令缓存失效,那么在数据库更新成功前,如果有另外一个请求访问了缓存,发现缓存数据库已经失效,于是就会按照数据获取策略,从数据库中使用这个已经陈旧的数值去更新缓存中的数据,这就导致这个过期的数据会长期存在于缓存中,最终导致数据不一致的严重问题。)
关键点二:数据库更新以后,需要令缓存失效,而不是更新缓存为数据库的最新值
(为什么呢?你想一下,如果两个几乎同时发出的请求分别要更新数据库中的值为 A 和 B,如果结果是 B 的更新晚于 A,那么数据库中的最终值是 B。但是,如果在数据库更新后去更新缓存,而不是令缓存失效,那么缓存中的数据就有可能是 A,而不是 B。因为数据库虽然是“更新为 A”在“更新为 B”之前发生,但如果不做特殊的跨存储系统的事务控制,缓存的更新顺序就未必会遵从“A 先于 B”这个规则,这就会导致这个缓存中的数据会是一个长期错误的值 A。)
2. Read-Through
(1)获取数据:
应用向缓存要求数据;
如果缓存中有数据,返回给应用,应用再将数据返回;
如果没有,缓存查询数据库,并将结果写入自己;
缓存将数据返回给应用。
这种情况下缓存系统彻底变成了它身后数据库的代理,二者成为了一个整体,应用的请求访问只能看到缓存的返回数据,而数据库系统对它是透明的。
虽然read-through和cache-aside非常相似,但至少有两个关键区别:
-
在cache-aside中,应用程序负责从数据库中获取数据并填充缓存。在read-through中,此逻辑通常由库或独立缓存提供程序支持。
-
与cache-aside不同,read-through cache中的数据模型不能与数据库中的数据模型不同
3. Write-Through
主要用于处理数据更新的场景。
(1)获取数据:
应用要求缓存更新数据;
如果缓存中有对应数据,先更新该数据;
缓存再更新数据库中的数据;缓存告知应用更新完成。
这里的一个关键点是,缓存系统需要自己内部保证并发场景下,缓存更新的顺序要和数据库更新的顺序一致。
(数据更新的异常情形:如果缓存更新失败,直接返回失败,没有数据不一致的情况发生;如果缓存更新成功,数据库更新失败,这种情况下需要回滚缓存中的更新,或者干脆从缓存中删除该数据。)
4. Write-Back
数据写入缓存和数据库更新是异步完成的,即数据写入缓存后立即返回,数据库更新异步完成。
(这种方式带来的最大好处是拥有最大的请求吞吐量,并且操作非常迅速,数据库的更新甚至可以批量进行,因而拥有杰出的更新效率以及稳定的速率,这个缓存就像是一个写入的缓冲,可以平滑访问尖峰。另外,对于存在数据库短时间无法访问的问题,它也能够很好地处理。但是它的弊端也很明显,异步更新一定会存在着不可避免的一致性问题,并且也存在着数据丢失的风险(数据写入缓存但还未入库时,如果宕机了,那么这些数据就丢失了)
总结:缓存在实际项目中应用非常多,而最常用的缓存模式就是 Cache-Aside,所以深入理解 Cache-Aside是关键。