软件架构中的跨层缓存
本文探讨了缓存的工作原理以及如何跨层使用缓存。
本文的目的是帮助读者了解什么是缓存,它所解决的问题,以及如何在系统架构的各层应用缓存来解决现代软件系统所面临的一些挑战。
本文针对的是软件开发人员、技术经理、软件架构师、测试工程师,或者其他有兴趣了解如何在软件系统中使用缓存的人。
什么是缓存?
为什么需要缓存
现代的软件系统已经变得越来越分散,越来越复杂。这带来了许多挑战和问题,特别是与系统性能有关的。系统的缓慢可能会导致公司的信誉和利润的损失。
下图显示了现代分布式架构的一个非常直接的观点。请注意,实际的架构会更加复杂,许多微服务(包括内部和外部)作为流程的一部分被执行。在系统的设计中,还会有额外的组件,如消息传递系统、LDAP、规则引擎等等。
图1:软件系统的层数
如上图所示,不同组件之间有很多互动,完成一个请求需要跳转。这就增加了每个接触点的延迟,这是由于组件的处理时间和等待下游组件的响应时的等待时间。
请注意,处理时间可能是由于应用程序本身或下游系统花费的时间--也可能是由于网络(DNS查询、建立连接、网络传输时间等)。
这就是缓存通过在应用程序的客户端/组件附近保持数据的副本,帮助使系统表现更好。
缓存如何工作
如上文图1所示,在一个软件系统中可能有不同的层和互动关系。缓存可以应用于任何一层,但缓存操作的基本原则是不变的。缓存数据将被用来避免昂贵的网络跳转、底层数据库的调用或缓慢的存储系统。
下图显示了在一个特定的场景下,缓存是如何工作的。这可以通过以下步骤的顺序来解释。请注意,在不同的场景中,缓存的实现可能会有变化,但在高层次上,方法是一样的:
- 一个系统/组件收到一个请求,将所请求的数据送回。
- 它将检查请求的数据是否在缓冲区内。
- 如果数据在缓存中,同样的缓存数据将被返回。
- 否则,系统将从源头获取数据(可以是数据库、外部系统或API调用,例如),并使用这些数据来填充缓存。
- 最后,这些数据将被返回给调用者。
使用缓存的优点
- 更好的应用性能:使用缓存的主要优点是它能提高应用程序的性能。由于所请求的数据通常在离应用更近的地方,在快速的内存访问中,它可以被返回并重新用于进一步处理。这有助于提高应用程序的性能。
- 避免不必要的磁盘访问/网络跳转:由于所请求的数据通常离应用更近,在快速的内存访问中,这可以帮助避免不必要的跳转到较慢的组件,如数据库、磁盘,或通过网络调用其他组件/系统。这又有助于提高应用程序的性能。
- 更好的数据库可扩展性: 因为现在对数据库的查询较少,它的容量被释放出来以处理其他请求。这可以减少数据库的负载/成本,提高可扩展性。这对其他后端系统/组件也是如此。
使用缓存时的重要考虑因素
在为一个特定的场景设计缓存框架时,必须做出一些重要的决定。下面是对缓存设计中一些关键方面的总结:
- 你应该缓存多少数据?
- 在缓存中插入新数据时,哪些数据必须被删除或保留?
- 缓存中的数据是否仍然相关,或者是陈旧的/过时的?
- 如何保持缓存中的数据是最新的?
- 如何保持缓存的低失误率?
缓存的类型/口味和各种缓存策略方法将在以下章节中讨论。我们将看看这些是如何影响缓存行为的,并解决上面提出的一些问题。
缓存的多种类型
在这一节中,我们将讨论通常用于管理底层数据存储被更新的场景的各种策略。正如上一节所提到的,缓存丢失率应该保持在低水平。
缓存丢失是指在缓存中找不到所请求的数据的情况,而缓存命中是指在缓存中找到所请求的数据,不需要从源头检索的情况。
为了保持低的缓存失误率,缓存数据应该保持最新。根据应用模式的不同,可以使用以下技术之一来确保缓存中的数据尽可能是最新的:
- 通过缓冲区写入:在这种技术中,数据首先在高速缓存中被更新,然后是源系统。这将确保高速缓冲区总是有最新的更新数据。然而,这在源系统上发生的写操作中引入了延迟。如果应用程序是写密集型的,不建议使用这种方法。
- 回写缓冲区:为了克服通过缓冲区写的问题,使用了这种技术。同样,在这种情况下,缓存也会首先被更新。然而,缓存中更新的数据会异步地同步回源系统。如果应用对源系统的一致性有更高的要求,这可能不是一个推荐的技术。
- 围绕高速缓存写:在这种技术中,数据直接在源系统中被更新。缓存将被定期刷新,以更新来自数据存储的数据。这有可能会得到陈旧的数据或增加缓存的丢失。
缓存刷新策略的例子
本节总结了一些用于刷新缓存内容的最流行的技术。有多种原因可能需要刷新缓存。由于缓存的大小一般比源文件小得多,所以不是所有的数据都能被缓存。随着时间的推移,缓冲区的大小不断增长,它可能会变得很满。用更需要或更常用的数据替换缓存中的旧数据,会使缓存失误率降低。下面是常用的刷新缓冲区的技术清单:
- 最近使用(MRU):在这个技术中,最近使用的项目首先被丢弃,然后被一个新的项目取代。
- 最近使用最少的(LRU):在这种技术中,最近使用最少的项目首先被丢弃,然后被一个新的项目取代。
- 先入先出(FIFO):在这种技术中,第一个被插入缓存的项目首先被丢弃,然后被一个新的项目所取代。
- 后进先出(LIFO):在这种技术中,在缓存中插入的最后一个项目首先被丢弃,然后被新的项目取代。
- 最不频繁使用(LFU):在这种技术中,缓存中使用最少的项目首先被丢弃,然后被新的项目取代。
- 最频繁使用(MFU):在这种技术中,缓存中使用频率最高的项目首先被丢弃,然后被新的项目取代。
跨层缓存
下表总结了在一个软件系统中如何跨层使用缓存。一些工具/框架也被强调了出来,这些工具/框架可以用来在特定的情况下实现缓存。
请注意,在一个软件应用中,缓存可以应用于一个或多个层:
层/组件 | 缓存的位置 | 缓存什么 | 工具/框架 |
网络客户端 | 网络/移动浏览器 | 图片/字体和媒体文件 | |
网络服务器 | 网络服务器 | 动态内容,避免应用服务器的过载 | 使用反向代理 |
CDN | 在内容交付网络上 | 静态以及API响应 | CDN如AWS CloudFront、Akamai等。 |
应用/服务层(L1) | 在应用服务器上 | 动态服务器响应 | 自定义逻辑或可以使用各种框架提供的内存功能,如Hibernate提供的内存。 |
分布式/内存缓存(L2) | 在一个单独的集群上,在内存中运行。可以被多个应用程序使用。 | 通常数据存储在键值对中。也可用于无状态应用程序的会话管理。 | Redis, MemCached, AWS ElasticCache, HazelCast |