编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

利用Spring Cache提升系统性能,缓存助力加速

wxchong 2024-07-29 08:04:15 开源技术 21 ℃ 0 评论


引言

首先SpringCache 并非某一种 Cache 实现的技术,Spring Cache是Spring框架提供的一个模块,SpringCache 是一种缓存实现的通用技术,基于 Spring 提供的 Cache 框架,让开发者更容易将自己的缓存实现高效便捷的嵌入到自己的项目中。

它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,可以快速的上手使用缓存功能。

为什么使用Spring Cache

我们发现网上有各种缓存框架,各有各的优势,比如Redis、Memcached、Guava、Caffeine等等。

如果我们的程序想要使用缓存,就要与这些框架耦合。聪明的架构师已经在利用接口来降低耦合了,利用面向对象的抽象和多态的特性,做到业务代码与具体的框架分离。

但我们仍然需要显式地在代码中去调用与缓存有关的接口和方法,在合适的时候插入数据到缓存里,在合适的时候从缓存中读取数据。但如果使用spring Cache能给我们带来如下好处:

1)简化缓存管理:Spring Cache提供了一套简单且统一的API和注解,使得缓存的添加、更新、失效等操作变得更加方便和易于管理。

2)降低对底层缓存实现的依赖:Spring Cache的抽象层帮助开发人员摆脱对具体缓存实现的绑定,灵活地切换不同的缓存提供者而不需要改动业务代码。

3)支持多种缓存实现:Spring Cache兼容多种主流的缓存提供者,如Ehcache、Redis、Guava等,可以根据需求选择适合的缓存实现方式。

4)增强代码可读性:通过在方法上添加缓存注解,可以清晰地标识出哪些方法会使用缓存,提高代码的可读性和可维护性。

如何使用Spring Cache

spring cache的使用非常简单,主要三步骤:加依赖,开启缓存,加缓存注解。

导入相关依赖

在pom.xml文件中添加Spring Cache以及具体缓存实现(这里以Ehcache为例)的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

配置Spring Cache

在Spring Boot应用中,可以通过在application.properties或application.yml中进行相关配置,如:

spring.cache.type=ehcache

编写Service类

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class MyDataService {

    @Cacheable(value = "myDataCache", key = "#id")
    public String getDataById(Long id) {
        System.out.println("Fetching data from database for id: " + id);
        return "Data" + id;
    }
}

调用Service方法

MyDataService类中的getDataById方法通过@Cacheable注解指定了缓存的名称为myDataCache,并指定了缓存的key为方法的参数id。当调用该方法时,如果缓存中存在对应key的数据,将直接从缓存中取出返回;否则会执行方法内部逻辑,并将结果放入缓存中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyDataController {

    @Autowired
    private MyDataService myDataService;

    @GetMapping("/data/{id}")
    public String getData(@PathVariable Long id) {
        return myDataService.getDataById(id);
    }
}

Spring Cache常用注解

Spring Cache提供了几个常用的注解,分别为@Cacheable、@CachePut、@CacheEvict、@Caching、 @CacheConfig。除了最后一个CacheConfig外,其余四个都可以用在类上或者方法级别上,如果用在类上,就是对该类的所有public方法生效,下面分别介绍一下这几个注解。

@Cacheable:

@Cacheble注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上

参数:

value :用来指定缓存组件的名字
key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用
cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。
condition :可以用来指定符合条件的情况下才缓存
unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
sync :是否使用异步模式。

@CachePut:

加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用。它通常用在新增方法上

参数:

value:指定缓存的名称,表示数据将被存储在哪个缓存中。这个参数是必须要指定的。
key:指定缓存项的key,可以使用SpEL表达式来动态生成key。如果不指定,则默认使用方法的所有参数作为key。
condition:指定一个SpEL表达式,只有当条件为true时才会将返回值存储到缓存中。默认为空字符串,表示始终存储到缓存。
unless:指定一个SpEL表达式,当条件为true时不会将返回值存储到缓存中。通常用于排除特定情况下的缓存操作。
keyGenerator:指定自定义的KeyGenerator实现类,用于生成缓存key。
cacheManager:指定使用的缓存管理器的名称,用于从多个缓存管理器中选择特定的一个。

@CacheEvict

使用了CacheEvict注解的方法,会清空指定缓存。一般用在更新或者删除的方法上

参数:

value:指定缓存的名称,表示要从哪个缓存中移除数据。这个参数是必须要指定的。
key:指定要移除的缓存项的key,可以使用SpEL表达式来动态生成key。如果不指定,则默认使用方法的所有参数作为key。
condition:指定一个SpEL表达式,只有当条件为true时才会执行缓存清除操作。默认为空字符串,表示始终清除缓存。
allEntries:指定是否移除缓存中所有的数据(默认为false)。设为true时将移除缓存中的所有数据,忽略key参数。
beforeInvocation:指定在方法执行之前还是之后执行缓存清除操作(默认为false)。设为true时,在方法执行之前就会清除缓存;设为false时,在方法执行成功后再清除缓存。

@Caching

Java注解的机制决定了,一个方法上只能有一个相同的注解生效。那有时候可能一个方法会操作多个缓存(这个在删除缓存操作中比较常见,在添加操作中不太常见)。

通过@Caching注解,开发者可以将多个缓存操作注解组合在一起,实现复杂的缓存逻辑。例如,在某个方法中可能既需要查询缓存又要更新缓存,或者需要在特定条件下清除缓存,这时可以使用@Caching注解来进行统一管理。这样可以减少重复代码、提高代码可读性,并灵活地控制缓存操作的行为。

参数:

cacheable:指定一个或多个@Cacheable注解,表示在方法执行前检查缓存中是否存在结果,并在缓存命中时直接返回缓存数据。
put:指定一个或多个@CachePut注解,表示无论缓存中是否存在相同key的数据,都会执行方法并更新缓存中对应的值。
evict:指定一个或多个@CacheEvict注解,表示从缓存中移除一个或多个缓存项。

Spring Cache集成Redis


导入相关依赖

从官方文档可以看到spring-boot-starter-data-redis 已经包含了jedis客户端,我们在使用jedis连接池的时候不必再添加jedis依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>


添加redis相关配置

server:
  port: 8080
spring:
  # redis相关配置
  redis:
    database: 0
    host: localhost
    port: 6379
    password: 123456
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒)默认是2000ms
    timeout: 2000ms
  cache:
    redis:
      ## Entry expiration in milliseconds. By default the entries never expire.
      time-to-live: 1d
      #写入redis时是否使用键前缀。
      use-key-prefix: true

启用缓存功能

在Spring Boot的主应用程序类上添加@EnableCaching注解,启用缓存功能。

编写Service类

创建一个Service类,定义一个简单的方法进行数据查询,并添加缓存注解使用Redis作为缓存提供者

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class MyDataService {

    @Cacheable(value = "myDataCache", key = "#id")
    public String getDataById(Long id) {
        System.out.println("Fetching data from database for id: " + id);
        return "Data" + id;
    }
}

示例Controller类: 创建一个Controller类,调用Service方法获取数据。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyDataController {

    @Autowired
    private MyDataService myDataService;

    @GetMapping("/data/{id}")
    public String getData(@PathVariable Long id) {
        return myDataService.getDataById(id);
    }
}

总结

Spring Cache是Spring框架提供的缓存抽象层,用于简化应用中对缓存的管理。通过使用Spring Cache,开发者可以轻松地将缓存集成到应用程序中,提高性能、降低数据库负载,并优化系统的响应时间,但也存在以下不足:

  • 不支持TTL,不能为每个 key 设置单独过期时间 expires time,
  • 针对多线程没有专门的处理,所以当多线程时,是会产生数据不一致性的。(同样,一般有高并发操作的缓存数据,都会特殊处理,而不太使用这种方式)

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表