首页 > 编程知识 正文

ios清理缓存(缓存刷新方式有哪些)

时间:2023-05-04 21:13:25 阅读:87405 作者:1620

来源: https://Coolshell.cn/Articles/17416.HTML更新缓存写数据代码的时候,很多人看到了先删除缓存再更新数据库,然后通过后续的操作重新加载数据的缓存。 但是,这在逻辑上是错误的。 请考虑两种同时操作。 一个是更新操作,另一个是查询操作。 更新操作删除缓存后,查询操作不会命中缓存。 首先读取旧数据并放入缓存,然后通过更新操作更新数据库。 然后,缓存中的数据仍然是旧数据,缓存中的数据很脏。 然后,就这样脏下去。

我不知道为什么这么多人用这个逻辑,但是在给微博发了这个贴之后,我发现很多人给了很多非常复杂奇怪的方案。 因此,我想写这篇文章谈几个缓存更新的设计模式。

在这里,我们不讨论缓存更新和数据更新这两件事是事务还是有可能失败。 首先,假设数据库更新和缓存更新成功(先写正确的成功代码逻辑)。

有四种设计模式可以更新缓存:缓存辅助、读缓存、写缓存和写缓存。 让我们来看看这四种类型的Pattern。

缓存辅助模式

这是最常用的pattern。 具体逻辑如下。

失效: APP首先从缓存中提取数据,如果未得到,则从数据库中提取数据,成功后放入缓存。 命中: APP从cache中获取数据,然后返回。 更新:将数据保存到数据库,成功后禁用缓存。

请注意,我们的更新首先更新数据库,成功后禁用缓存。 那么,这种方式没有文章前面提到的那个问题也可以吗? 我们可以脑补一下。

一个是查询操作,一个是更新操作的同时执行。 首先,删除缓存数据的操作消失了,首先更新了数据库中的数据。 此时,由于缓存仍然有效,并发查询操作有未更新的数据,但更新操作会立即禁用缓存,后续的查询操作会从数据库中提取数据。 正如开头的逻辑中出现的问题一样,后续的查询操作将采用旧数据。

这是标准的设计模式,Facebook论文《Scaling Memcache at Facebook》也使用了这个策略。 为什么不在写入数据库后更新缓存? 位于Quora的这个问答集; 请看a0103010。 主要通过2个同时写入可能会产生脏数据。

那么,所谓的Cache Aside是不是没有并发问题呢? 不,例如,一个是读取操作,但不会命中缓存。 然后,我去数据库取数据。 此时,写入操作完成了。 写入数据库后,禁用缓存。 然后,用之前的读取操作放入旧数据。 所以,会变成肮脏的数据。

但是,这个case理论上会发生,但实际发生的概率可能非常低。 这是因为在读缓存期间缓存将被禁用,并且写入必须同时进行。 实际上,数据库的写入速度要比读取慢得多,表也会被锁定。 读取操作必须在写入操作之前进入数据库操作,并且缓存必须比写入操作更新。 所有这些条件都满足的概率很低。

因此,这就像Quora上的那个回答一样,要么用2PC或Paxos协议来保证一致性,要么在并发时拼命降低数据被污染的概率,但是Facebook使用的就是降低这个概率的游戏。 因为2PC太慢了,PAX操作系统太复杂了。 当然,建议设定缓存的有效期限。

读/写路径

rn

我们可以看到,在上面的Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。所以,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

Read Through

Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

下图自来Wikipedia的Cache词条。其中的Memory你可以理解为就是我们例子里的数据库。

Write Behind Caching Pattern

Write Behind 又叫 Write Back。一些了解Linux操作系统内核的同学对write back应该非常熟悉,这不就是Linux文件系统的Page Cache的算法吗?是的,你看基础这玩意全都是相通的。所以,基础很重要,我已经不是一次说过基础很重要这事了。

Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

在wikipedia上有一张write back的流程图,基本逻辑如下:

再多唠叨一些

1)上面讲的这些Design Pattern,其实并不是软件架构里的mysql数据库和memcache/redis的更新策略,这些东西都是计算机体系结构里的设计,比如CPU的缓存,硬盘文件系统中的缓存,硬盘上的缓存,数据库中的缓存。基本上来说,这些缓存更新的设计模式都是非常老古董的,而且历经长时间考验的策略,所以这也就是,工程学上所谓的Best Practice,遵从就好了。

2)有时候,我们觉得能做宏观的系统架构的人一定是很有经验的,其实,宏观系统架构中的很多设计都来源于这些微观的东西。比如,云计算中的很多虚拟化技术的原理,和传统的虚拟内存不是很像么?Unix下的那些I/O模型,也放大到了架构里的同步异步的模型,还有Unix发明的管道不就是数据流式计算架构吗?TCP的好些设计也用在不同系统间的通讯中,仔细看看这些微观层面,你会发现有很多设计都非常精妙……所以,请允许我在这里放句观点鲜明的话——如果你要做好架构,首先你得把计算机体系结构以及很多老古董的基础技术吃透了。

3)在软件开发或设计中,我非常建议在之前先去参考一下已有的设计和思路,看看相应的guideline,best practice或design pattern,吃透了已有的这些东西,再决定是否要重新发明轮子。千万不要似是而非地,想当然的做软件设计。

4)上面,我们没有考虑缓存(Cache)和持久层(Repository)的整体事务的问题。比如,更新Cache成功,更新数据库失败了怎么吗?或是反过来。关于这个事,如果你需要强一致性,你需要使用“两阶段提交协议”——prepare, commit/rollback,比如Java 7 的XAResource,还有MySQL 5.7的 XA Transaction,有些cache也支持XA,比如EhCache。当然,XA这样的强一致性的玩法会导致性能下降,关于分布式的事务的相关话题,你可以看看《分布式系统的事务处理》一文。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。