读书笔记《building-microservices-with-spring》使用缓存模式提高应用程序性能
在前面的章节中,我们已经了解了 Spring 如何在后端访问应用程序的数据。我们还看到了 Spring JDBC 模块如何为数据库访问提供 JdbcTemplate
帮助类。 Spring 提供与 Hibernate、JPA、JDO 等 ORM 解决方案集成的支持,并跨应用程序管理事务。现在,在本章中,我们将看到 Spring 如何提供缓存支持来提高应用程序性能。
当您深夜从办公室回到家时,您是否曾经遇到过妻子的一连串问题?是的,我知道在你累了和筋疲力尽的时候回答这么多问题是很烦人的。当你一遍又一遍地被问到同样的问题时,更令人恼火..
有些问题可以用 Yes 或 No 来回答,但是对于某些问题,您必须详细解释。考虑一下如果一段时间后您再次被问到另一个冗长的问题会发生什么!类似地,应用程序中有一些无状态组件,这些组件的设计方式使得它们一遍又一遍地询问相同的问题以单独完成每个任务。类似于你老婆问的一些问题,系统中的一些问题需要一段时间才能获取到合适的数据——它可能背后有一些主要的复杂逻辑,或者它必须从数据库中获取数据,或者调用远程服务。
如果我们知道某个问题的答案不太可能经常更改,那么我们可以记住该问题的答案,以备以后被同一系统再次询问时使用。通过相同的渠道再次获取它是没有意义的,因为它会影响您的应用程序的性能,并且会浪费您的资源。在企业应用程序中,缓存是一种存储那些经常需要的答案的方法,这样我们就可以从缓存中获取,而不是通过适当的渠道一遍又一遍地获取同一个问题的答案。在本章中,我们将讨论 Spring 的缓存抽象特性,以及 Spring 如何以声明方式支持缓存实现。它将涵盖以下几点:
- What is a cache?
- Where do we do this caching?
- Understanding the cache abstraction
- Enabling caching via the Proxy pattern
- Declarative Annotation-based caching
- Declarative XML-based caching
- Configuring the cache storage
- Implementing custom cache annotations
- Caching best practices
让我们开始。
基本上,Java applications 中的缓存应用于 Java 方法,以减少缓存中可用相同信息的执行次数.这意味着,无论何时调用这些 Java 方法,缓存抽象都会根据给定的参数将缓存行为应用于这些方法。如果给定参数的信息已经在缓存中可用,则无需执行目标方法即可返回。如果缓存中没有所需的信息,则执行 target
方法,并将结果缓存并返回给调用者。缓存抽象还提供其他与缓存相关的操作,例如更新和/或删除缓存中的内容。当应用程序中的数据有时发生变化时,这些操作很有用。
Spring Framework 通过使用 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
为 Spring 应用程序提供缓存抽象接口。缓存需要使用实际存储来存储缓存数据。但是缓存抽象只提供缓存逻辑。它不提供任何物理存储来存储缓存数据。因此,开发人员需要在应用程序中实现缓存的实际存储。如果您有一个分布式应用程序,那么您将需要相应地配置您的缓存提供程序。这取决于您的应用程序的用例。您可以为分布式应用程序跨节点制作相同数据的副本,也可以制作集中式缓存。
市场上有几个缓存提供程序,您可以根据您的应用程序要求使用它们。其中一些如下:
- Redis
OrmLiteCacheClient
Memcached
- In Memory Cache
- Aws DynamoDB Cache Client
- Azure Cache Client
要在应用程序中实现缓存抽象,您必须处理以下任务:
- Caching declaration: This means that you have to recognize those methods in the application that need to be cached, and annotate these methods either with caching annotations, or you can use XML configuration by using Spring AOP
- Cache configuration: This means that you have to configure the actual storage for the cached data--the storage where the data is stored and read from
现在让我们看看如何在 Spring 应用程序中启用 Spring 的缓存抽象。
您可以通过以下两种方式启用 Spring 的 cache 抽象:
- Using Annotation
- Using the XML namespace
Spring 通过使用 AOP 透明地将缓存应用于 Spring bean 的方法。 Spring applies 代理围绕 Spring bean 声明需要缓存的方法。此代理将缓存的动态行为添加到 Spring bean。下图说明了缓存行为:
在上图中,您可以看到 Spring 将 Proxy 应用于 AccountServiceImpl< /span> 类来添加缓存行为。 Spring 使用 GoF 代理模式在应用程序中实现 caching。
让我们看看如何在 Spring 应用程序中启用此功能。
如您所知,Spring 提供了 很多特性,但它们大多是禁用的。您必须在使用前启用这些功能。如果你想在你的应用中使用Spring的缓存抽象,你必须启用这个特性。如果您使用 Java 配置,您可以通过将 @EnableCaching
注释添加到您的配置类之一来启用 Spring 的缓存抽象。以下配置类显示了 @EnableCaching
注释:
在前面的Java配置文件中,我们在配置类AppConfig.java
中添加了@EnableCaching
注解;此注解指示 Spring Framework 为应用程序启用 Spring 缓存行为。
现在让我们看看如何使用 XML 配置启用 Spring 的缓存抽象。
在 Spring 应用中,Spring 的 abstraction 为缓存声明提供了如下注解:
@Cacheable
: This indicates that before execution of the actual method, look at the return value of that method in the cache. If the value is available, return this cached value, if the value is not available, then invoke the actual method, and put the returned value into the cache.@CachePut
: This updates the cache without checking if the value is available or not. It always invokes the actual method.@CacheEvict
: This is responsible for triggering cache eviction.@Caching
: This is used for grouping multiple annotations to be applied on a method at once.@CacheConfig
: This indicates to Spring to share some common cache-related settings at the class level.
现在让我们仔细看看每个注释。
@Cacheable
标记一个用于缓存的方法。其结果存储在缓存中。对于具有相同参数的该方法的所有后续调用,它将使用键从 缓存中获取数据。该方法将不会被执行。以下是 @Cacheable
属性:
- value: This is the name of cache to use
- key: This is the key for each cached data item
- condition: This is a SpEL expression to evaluate true or false; if it is false, then the result of caching is not applied to the method call
- unless: This too is a SpEL expression; if it is true, it prevents the return value from being put in the cache
您可以使用 SpEL 和方法的参数。让我们看看下面的代码,了解 @Cacheable
注解的最简单声明。它需要与该方法关联的缓存的名称。请参考以下代码:
在上述代码中,findAccount
方法使用 @Cacheable
注释进行了注释。这意味着此方法与缓存相关联。缓存的名称是 accountCache。每当为特定的 accountId
调用此方法时,都会检查缓存是否返回 value给定 accountId
的此方法的。您还可以为缓存指定多个名称,如下所示:
如前所述,@Cacheable
和 @CachePut
注解都有相同的目标,即填充缓存。但它们的工作方式与 略有不同。 @CachePut
标记一个用于缓存的方法,其结果存储在缓存中。对于具有相同参数的该方法的每次调用,它总是调用实际方法而不检查该方法的返回值是否在缓存中可用。以下是 @CachePut
属性:
- value: This is the name of the cache to use
- key: This is the key for each cached data item
- condition: This is a SpEL expression to evaluate true or false; if false, then the result of caching is not applied to the method call
- unless: This is also a SpEL expression; if it is true, it prevents the return value from being put in the cache
您还可以将 SpEL 和方法的参数用于 @CachePut
注释。以下代码是 @CachePut
注解的最简单声明:
上述代码中,当调用save()
时,会保存Account
。然后将返回的 Account 放入 accountCache
缓存中。
如前所述,缓存由基于方法参数的方法填充。它实际上是一个默认的缓存键。在 @Cachable
注释的情况下,findAccount(Long accountId)
方法具有 accountId< /code> 作为参数,
accountId
用作此方法的缓存键。但是在 @CachePut
注解的情况下,save()
的唯一参数是一个帐户。它用作缓存键。使用 Account
作为缓存键似乎不太好。在这种情况下,您需要缓存键是新保存的帐户的 ID,而不是帐户本身。因此,您需要自定义密钥生成行为。让我们看看如何自定义缓存键。
您可以通过使用 @Cacheable
的key属性自定义缓存键和 @CachePut
注释。缓存键由 SpEL 表达式派生,该表达式使用对象的属性作为以下代码片段中突出显示的键属性。让我们看看以下示例:
您可以在前面的代码片段中看到我们是如何使用 @Cacheable
注释的 key 属性创建缓存键的。
让我们看看 Spring 应用程序中这些注解的另一个属性。
Spring 的缓存注释允许您通过使用 的条件属性关闭某些情况下的缓存@Cacheable
和 @CachePut
注释。这些被赋予一个 SpEL 表达式来评估条件值。如果条件表达式的值为真,则缓存该方法。如果条件表达式的值为false,则该方法不被缓存,而是每次都执行而不执行任何缓存操作,无论缓存中的值是什么或使用什么参数。让我们看一个例子。仅当传递的参数的值大于或等于 2000
时,才会缓存以下方法:
@Cacheable
和 @CachePut
注释还有一个属性——除非< /代码>。这也给出了一个 SpEL 表达式。此属性可能看起来与条件属性相同,但它们之间存在一些差异。与条件不同,
unless
表达式在方法被调用后进行评估。它可以防止将值放入缓存中。我们看下面的例子——我们只想在bank name不包含HDFC时缓存:
正如您在前面的代码片段中看到的,我们使用了两个属性——condition
和 unless
。但是 unless
属性的 SpEL 表达式为 #result.bankName.contains('HDFC')
。在此表达式中,结果是 SpEL 扩展或缓存 SpEL 元数据。以下是 SpEL 中可用的缓存元数据列表:
表达式 |
说明 |
|
缓存方法的名称 |
|
缓存的方法,即被调用的方法 |
|
它评估正在调用的目标对象 |
|
它评估被调用的目标对象的类 |
|
执行当前方法的缓存数组 |
|
传入缓存方法的参数数组 |
|
缓存方法的返回值;仅在 |
Note
Spring 的 @CachePut
和 @Cacheable
注解不应该用在同一个方法上,因为它们有不同的行为。 @CachePut
注释强制执行缓存方法以更新缓存。但是 @Cacheable
注解只有在方法的返回值在缓存中不可用的情况下才会执行缓存的方法。
您已经了解了如何在 Spring 应用程序中使用 Spring 的 @CachePut
和 @Cacheable
注释向缓存中添加信息。但是我们如何才能从缓存中删除这些信息呢? Spring 的缓存抽象提供了另一个用于从缓存中移除缓存数据的注解——@CacheEvict
注解。让我们看看如何使用 @CacheEvict
注释从缓存中删除缓存的数据。
Spring 的缓存抽象不仅允许填充缓存,还允许从缓存中删除缓存的数据。在应用程序中有一个阶段,您必须从缓存中删除陈旧或未使用的数据。在这种情况下,您可以使用 @CacheEvict
注释,因为它不像 @Cacheable
那样不会向缓存添加任何内容注解。 @CacheEvict
注解仅用于执行缓存驱逐。让我们看看这个注解如何使 AccountRepository
的 remove()
方法成为缓存驱逐:
正如您在前面的代码片段中看到的,与参数 accountId 关联的 value
,在调用 remove()
方法时从 accountCache
缓存中删除。以下是 @Cacheable
属性:
- value: This is an array of names of the cache to use
- key: This is a SpEL expression to evaluate the cache key to be used
- condition: This is a SpEL expression to evaluate true or false; if it is false, then the result of caching is not being applied to the method call
- allEntries: This implies that if the value of this attribute is true, all entries will be removed from the caches
- beforeInvocation: This means that if the value of this attribute is true, the entries are removed from the cache before the method is invoked, and if the value of this attribute is false (the default), the entries are removed after a successful method invocation
Spring 的缓存抽象允许您使用多个 annotations 通过使用 @Caching
注释。 @Caching
注解将其他注解分组,例如 @Cacheable
、@CachePut
, 和 @CacheEvict
用于相同的方法。例如:
Spring 的缓存抽象允许 you 在类级别注释 @CacheConfig
以避免在每种方法中重复提及。在某些情况下,将缓存的自定义应用于所有方法可能非常乏味。在这里,您可以对类的所有操作使用 @CacheConfig
注解。例如:
您可以在前面的代码片段中看到,@CacheConfig
注解用于类级别,它允许您共享 accountCache
使用所有
cacheable
方法进行缓存。
Note
由于 Spring 的缓存抽象模块使用代理,因此您应该仅将缓存注释与公共可见性方法一起使用。在所有非公共方法中,这些注释不会引发任何错误,但使用这些注释注释的非公共方法不会显示任何缓存行为。
我们已经看到 Spring 还提供 XML 命名空间来在 Spring 应用程序中配置和实现缓存。让我们在下一节中看看如何。
保持您的配置代码缓存与业务代码分开,并保持Spring特定注释和源之间的松散耦合代码中,基于 XML 的缓存配置比基于注释的配置要优雅得多。因此,要使用 XML 配置 Spring 缓存,让我们使用缓存命名空间和 AOP 命名空间,因为缓存是 AOP 活动,它使用声明式缓存行为背后的代理模式。
您可以在前面的 XML 文件中看到,我们已经包含了 cache
和 aop
命名空间。缓存命名空间使用以下元素定义缓存配置:
XML 元素 |
缓存说明 |
|
相当于Java配置中的 |
|
它定义了缓存建议 |
|
它相当于 |
|
相当于 |
|
它相当于 |
|
它相当于 |
让我们看看以下基于 XML 配置的示例。
创建配置文件,spring.xml
如下:
在前面的 XML 配置文件中,突出显示的代码是 Spring 缓存配置。在缓存配置中,首先看到的是声明的 <aop:config>
然后是 <aop:advisor>
,它引用了 ID 为 cacheAccount
的通知,并且还有一个切入点表达式来匹配通知。建议使用 <cache:advice>
元素声明。这个元素可以有很多 <cache:caching>
元素。但是,在我们的示例中,我们只有一个 <cache:caching>
元素,它有一个 <cache:cacheable>
元素,一个 <cache:cache-put>
和一个 <cache:cache-evict>
元素;每个都将切入点中的方法声明为可缓存的。
让我们看一下带有缓存注解的应用程序的 Service
类:
在前面的文件定义中,我们使用了 Spring 的缓存注解在应用程序中创建缓存。现在让我们看看如何在应用程序中配置缓存存储。
Spring 的缓存抽象提供了很多 storage 集成。 Spring 为每个内存存储提供 CacheManager
。您可以使用应用程序配置 CacheManager
。然后 CacheManager
负责控制和管理缓存。让我们探索如何在应用程序中设置 CacheManager
。
Spring 的 SimpleCacheManager
可用于测试,但没有 cache 控制选项(溢出,驱逐)。所以我们必须使用第三方替代品,如下所示:
- Terracotta's EhCache
- Google's Guava and Caffeine
- Pivotal's Gemfire
让我们看一下第三方缓存管理器的配置之一。
Ehcache 是最受欢迎的 之一缓存提供者。 Spring 允许您通过在应用程序中配置 EhCacheCacheManager
来与 Ehcache 集成。以 以下 Java 配置为例:
上述代码中,bean方法cacheManager()
创建了一个EhCacheCacheManager
的对象,并用CacheManager
。在这里,Ehcache 的 CacheManager
被注入到 Spring 的 EhCacheCacheManager
中。第二个 bean 方法 ehCacheManagerFactoryBean()
创建并返回 EhCacheManagerFactoryBean
的实例。因为它是一个工厂 bean,它会返回一个 CacheManager
的实例。 XML 文件 ehcache.xml
具有 Ehcache 配置。 ehcache.xml
我们参考如下代码:
ehcache.xml
文件的内容因应用程序而异,但您至少需要声明一个最小缓存。例如,以下 Ehcache 配置声明了一个名为 accountCache 的缓存,具有 50 MB 的最大堆存储空间和 100 秒的生存时间:
Spring 的缓存抽象允许您为应用程序创建自定义 caching 注释,以识别缓存填充或缓存驱逐的缓存方法。 Spring 的 @Cacheable
和 @CacheEvict
注解用作 Meta 注解来创建自定义缓存注解。让我们看一下应用程序中自定义注释的以下代码:
在前面的代码片段中,我们定义了一个名为 SlowService
的自定义注解,使用 Spring 的 @Cacheable
注解进行注解。如果我们在应用程序中使用 @Cacheable
,那么我们必须将其配置为如下代码:
让我们用我们定义的自定义注解替换前面的配置,代码如下:
如您所见,我们仅使用 @SlowService
注释来使方法在应用程序中可缓存。
现在让我们进入下一部分,我们将在其中看到在应用程序中实现缓存时应该考虑哪些最佳实践。
在您的企业 Web 应用程序中,正确使用 caching 可以非常快速地呈现网页,最大限度地减少数据库命中,并减少服务器的内存、网络等资源的消耗。缓存是一种非常强大的技术,可以通过将陈旧数据存储在缓存中来提高应用程序的性能。以下是在设计和开发 Web 应用程序时应该考虑的最佳实践:
- In your Spring web application, Spring's cache annotations such as
@Cacheable
,@CachePut
, and@CacheEvict
should be used on concrete classes instead of application interfaces. However, you can annotate the interface method as well, using interface-based proxies. Remember that Java annotations are not inherited from interfaces, which means that if you are using class-based proxies by setting the attributeproxy-target-class="true"
, then Spring cache annotations are not recognized by the proxying. - If you have annotated any method with the
@Cacheable
, @CachePut, or@CacheEvict
annotations, then never call it directly by another method of the same class if you want to benefit from the cache in the application. This is because in direct calling of a cached method, the Spring AOP proxy is never applied. - In an enterprise application, Java Maps or any key/value collections should never be used as a Cache. Any key/value collection cannot be a Cache. Sometimes, developers use java map as a custom caching solution, but it is not a caching solution, because Cache provides more than a key/value storage, like the following:
- Cache provides eviction policies
- You can set the max size limit of Cache
- Cache provides a persistent store
- Cache provides weak reference keys
- Cache provides statistics
- The Spring Framework provides the best declarative approach to implement and configure the Cache solution in an application. So, always use the cache abstraction layer--it provides flexibility in the application. We know that the
@Cacheable
annotation allows you to separate business logic code from the caching cross-cutting concern. - Be careful whenever you use cache in the application. Always use cache in a place where it is actually required such as a web service or an expensive database call, because every caching API has an overhead.
- At the time of cache implementation in an application, you have to ensure that the data in the cache is in sync with the data storage. You can use distributed cache managers like Memcached for proper cache strategy implementation to provide considerable performance.
- You should use cache only as second option if data fetching is very difficult from the database because of slow database queries. It is because, whenever we use caching behavior in the application, first the value is checked in the cache if not available then it execute actual method, so it would be unnecessary.
- The Spring Framework provides the best declarative approach to implement and configure the Cache solution in an application. So, always use the cache abstraction layer--it provides flexibility in the application. We know that the
在本章中,我们看到了缓存如何帮助提高应用程序的性能。缓存主要作用于应用程序的服务层。在您的应用程序中,有一个方法返回的数据;如果应用程序代码根据相同的需求一遍又一遍地调用它,我们可以缓存该数据。缓存是避免针对相同需求执行应用程序方法的好方法。每当第一次调用此方法时,特定参数的方法的返回值都会存储在缓存中。对于相同参数的相同方法的进一步调用,将从该缓存中检索该值。缓存通过避免针对相同答案(例如执行数据库查询)的一些资源和时间消耗操作来提高应用程序性能。
Spring 提供缓存管理器来管理 Spring 应用程序中的缓存。在本章中,您已经了解了如何为特定的缓存技术定义缓存管理器。 Spring为缓存提供了一些注解如@Cacheable
、@CachePut
、< /em> 和 @CacheEvict
,我们可以在 Spring 应用程序中使用它们。我们还可以使用 XML 配置在 Spring 应用程序中配置缓存。 Spring 框架提供了缓存命名空间来实现这一点。 <cache:cacheable>
、<cache:cache-put>
和 <cache:cache-evict>
元素被用来代替相应的注解。
Spring 使用面向方面的编程使管理应用程序中的缓存成为可能。缓存是 Spring Framework 的一个横切关注点。这意味着,缓存是 Spring 应用程序的一个方面。 Spring 通过使用 Spring AOP 模块的环绕通知来实现缓存。