32.2 缓存抽象

缓存(Cache) vs 缓冲区(Buffer)

缓存和缓冲区两个术语往往可以互换着使用。但注意,它们代表着不同的东西。
缓冲区是作用于快和慢速实体之间的中间临时存储。
一块缓冲区必须等待其他并影响性能,通过允许一次性移动整个数据块而不是小块来缓解。数据从缓冲区读写只有一次。因此缓冲区对至少一方是可见的。
另一方面,缓存根据定义是隐性的,双方不会知道缓存的发生。它提高了性能,但允许以快速的方式多次读取相同的数据。

想了解更多这二者之间的差异,见:https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache

核心上,抽象将缓存作用于 Java 方法上,基于缓存中的可用信息,可以减少方法的执行次数。也就是说,每次目标方法的调用时,抽象使用缓存行为来检查执行方法,检查执行方法是否给定了缓存的执行参数:如果有,则返回缓存结果,不执行具体方法;如果没有,则执行方法,并将结果缓存后,返回给用户。以便于下次调用方法时,直接返回缓存的结果。这样,只要给定缓存执行参数,在复杂的方法(无论是 CPU 或者 IO 相关)只需要执行一次,就可以得到结果,并利用缓存可重复使用结果,而不必再次执行该方法。另外,缓存逻辑可以被透明地调用,不会对调用者造成任何的困扰。

显然,这种方法只适用于为某个给定输入(或参数)返回相同输出(结果),无论执行多少次。

抽象提供的其他缓存相关操作,比如更新缓存内容或者删除其中一条缓存。如果在应用程序过程中,发生了变化的数据需要缓存,那这些功能会很有用。

比如 Spring 框架其他服务一样,缓存服务是一种抽象(不是缓存的实现),并且需要使用实际的存储器来存储缓存数据。也就是说,抽象能够使开发人员不必编写缓存逻辑,但它没有提供缓存的存储器。这个抽象是由 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口实现的。

有些抽象的实现是开箱即用的:基于 JDK java.util.concurrent.ConcurrentMap 缓存实现,Ehcache 2.x,Gemfire cache, Caffeine 和 JSR-107 缓存(例如 Ehcache 3.x)。有关缓存存储/提供的更多信息,请参见 32.7 节 《Plugging-in different back-end caches》。

缓存抽象没有特别处理多线程和多进程环境,因为这些功能由缓存实现来处理...

如果是多进程环境(即部署在多个节点上的应用程序),则需要相应的配置程序提供缓存。根据使用情况,几个节点上相同数据的副本可能足够多了,但如果在应用程序过程中更改了数据,则需要启动其他传播机制,进行同步缓存数据。

缓存一个特定的对象是典型的缓存交互 get-if-not-found-then-proceed-and-put-finally 代码块:不需应用任何锁,并且几个线程同时尝试加载相同的对象。同样适用于回收,如果多个线程同时更新或者回收数据,则可能会使用过时的数据。某些缓存提供者在该领域提供高级功能,请参考您正在使用的缓存提供者的更多高级功能的详细信息。

要使用缓存抽象,开发人员需要注意两个方面:

  • 缓存声明 - 标志缓存的方法及缓存策略
  • 缓存配置 - 数据读写的缓存数据库