文章目录
- 简介
- 常规缓存
- 缓存操作
- 读缓存
- 写缓存
- 缓存回收策略
- LRU实现
- 在哪里放置缓存?
- 何时实现缓存?
- Python中的缓存
- 参考文献
简介
缓存,是软件从硬件中获取灵感的概念。
缓存是一个临时存储区域,用于存储使用过的东西以便于访问。
常规缓存
在计算机科学中,缓存是存储计算结果以快速访问的硬件组件。影响速度的主要因素是它的内存大小和位置。缓存的内存大小比RAM小得多。减少了检索数据的扫描次数。缓存位于更靠近CPU的位置,因此延迟更少。
缓存操作
缓存,如浏览器缓存,服务器缓存,代理缓存,硬件缓存。无论哪种缓存,操作有两种主要类型:read
和 write
当处理缓存时,会使用一个巨大内存块,取代从数据库,硬盘等耗时的读写,使工作更快。
读缓存
读缓存是存储被访问项的存储器。每次客户端从存储器请求数据时,请求都会命中与存储器相关联的缓存。
- 如果请求的数据在缓存上可用,那么缓存命中 Cache Hit
- 如果没有,则是缓存丢失 Cache Miss
- 当从缓存访问数据时,其他一些进程在此时更改数据,需要用新更改的数据替换缓存,这是缓存失效 Cache Invalidation
写缓存
写缓存指快速写入。写数据库代价很高,想象一个写量很大的系统,使用缓存可以处理数据库写负载,然后批量更新到数据库。需要注意的是,DB和缓存之间的数据应该始终是同步的。有三种方法可以实现写缓存:
- 直写 Write Through
对数据库的写入是通过缓存进行的。每次在缓存中写入新数据时,都会在数据库中进行更新。
优点:缓存和存储之间不会有数据不匹配
缺点:缓存和存储都需要更新,会增加开销,而不是提高性能 - 回写 Write Back
回写是指缓存以设定的时间间隔将值异步更新到数据库,这种方法与 Write Through 的优缺点正好相反。
优点:缓存和存储不需要都更新
缺点:缓存和存储之间会有数据不匹配 - 绕写 Write Around
将数据直接写入存储,并仅在读取数据时加载缓存。
优点:①缓存不会因写入后没有立即读取而超负荷。 ②减少 Write Through 的延迟
缺点:读取最近写入的数据会导致缓存丢失
缓存使读写速度更快,只有从缓存中读取和写入数据而不是从数据库中才有意义。但是,请记住,速度的提高只是因为缓存很小。缓存越大,搜索所需的时间就越长。
所以空间优化是很重要的。一旦缓存满了,我们只能通过删除缓存中已经存在的数据来为新数据腾出空间。
缓存回收策略用于决定哪些数据需要从缓存中丢弃:
- LRU - 最久未使用
当缓存耗尽空间时,删除最久未使用的元素。简单,易于实现,相对公平的缓存频率。 - LFU - 最近最少使用
同时考虑了数据的年龄和频率。但问题是,经常使用的数据会在缓存中停留很长时间。 - MRU - 最频繁使用
什么情况用到?例如,看图片时往回看的概率比较小。 - FIFO - 先进先出
缓存像队列一样工作,先进先出。非常适合按顺序读取和处理数据管道的情况。
缓存基本上是一个哈希表。每个进入它的数据都被散列和存储,使它可以以O(1)访问。
可以使用双重链接的链表实现。每次访问时向链表中添加一个项目,并维护它是哈希表中的一个引用,使能够O(1)访问。当元素已经存在时,将其从当前位置删除,并将其添加到链表的末尾。
缓存离消费者越近,速度就越快。这意味着在Web应用程序的情况下将缓存与Web服务器放在一起。但有几个问题:
- 当服务器宕机时,会丢失与服务器缓存相关的所有数据。
- 当需要增加缓存的大小时,会入侵分配给服务器的内存。
最可行的解决方案是在服务器外部维护缓存。尽管它包含了额外的延迟,但对于缓存的可靠性来说还是值得的。
分布式缓存的概念是在服务器外部托管缓存并独立地扩展它。
寻找技术实现缓存是所有步骤中最简单的。缓存保证了高速API,不使用缓存可能很笨,但如果出于错误的原因这样做,只会给系统增加额外的开销。所以实现前确保:
- 数据存储的点击率很高
- 已经尽了一切可能来提高DB级别的速度
- 已经学习和研究了各种缓存方法和系统,并找到了项目适合的方法和系统
推荐阅读:
使用 functools
模块中的 lru_cache()
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
def fib1(n):
if n < 2:
return n
return fib1(n - 1) + fib1(n - 2)
result = [fib(i) for i in range(16)]
print(result)
print(fib.cache_info()) # 查看命中和未命中次数
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
# CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)