利用Guava Cache作为本地缓存

利用Guava Cache作为本地缓存

前言

虽然我们一般都用Redis缓存但是,在一些小的项目中可以不使用Redis,而是利用Guava Cache来做本地缓存,这么做本地缓存的速度会比Redis要快一点。

虽然Spring Boot默认使用的是 SimpleCacheConfiguration。但是这个Guava Cache在其基础上,有了不同程度的优化。所以在做一些小项目的时候呢,可以用这个来做缓存。

内部实现

Guava Cache使用 ConcurrentMapCacheManager来实现的缓存

Guava Cache 与 ConcurrentMap二者很相似,但也不完全一样。最基本的区别是 ConcurrentMap 会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache 为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管 LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

区别

Redis自不用说,是一个在多个服务器之间能够实现集群和哨兵的缓存,相比之下,Guava Cache就稍稍弱了些。

如果数据量不大,且多个服务之间没有相互同步数据的需要,则使用Guava Cache是非常理想的,它使用简单且性能很好。如果需要缓存的数据量很大或者多个服务之间需要共享缓存数据,则redis是理想的选择,虽然读取性能对比Guava Cache满了很多,但是绝对值并不大,大多数情况下满足我们的需要。

还有一点比较大区别就是他们的拷贝程度不同。

Redis 缓存是深拷贝

从 Redis 中获取缓存时,系统中的数据对象是 Redis 缓存的副本。对该对象的任何操作都不会影响 Redis 中的缓存,后续再次获取还是修改之前的数据。除非执行Redis的更新操作。

Guava本地缓存直接获取则是浅拷贝

以获取一个MAP为例:如果直接从缓存中取,则是浅拷贝。
对缓存数据的任何操作都会同时修改缓存中的数据,下次从缓存中获取则是修改之后的数据。一般不会修改从缓存中获取到的数据,但如果要修改,则需注意Redis和Guava的不同。

所以如果只是做一个比较小的项目。就不用特地去连接redis。还是使用我们本地的缓存更为快速。

实现

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.24.RELEASE</version>
</dependency>

Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableCaching
public class GuavaCacheConfig {

@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(
CacheBuilder.newBuilder().
expireAfterWrite(10, TimeUnit.SECONDS).
maximumSize(1000));
return cacheManager;
}
}

这里是初始化一个对象并设置它的存活时间等等。我们把这个方法注入到spring的上下文当中。而每当我们需要去数据库读取数据时,spring就会先返回这个缓存的实体,将数据先存入到本地缓存当中。

这里我们设置它的存活时间是十秒。

用户类

首先需要一个实体类和与其对应的数据表。这也就只列出实体类,大家可以自己对应一下。

1
2
3
4
5
6
@Data
public class User {
private Long userId;
private String userName;
private Integer userAge;
}

当然。还有配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mybatis:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: com.example.guavacache.pojo



spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springtest?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver

server:
port: 8888

服务层

然后就是服务层,要实现的方法。

1
2
3
4
5
public interface UserMapper {
List<User> getUsers();
int addUser(User user);
List<User> getUsersByName(String userName );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserService {
@Autowired
private UserMapper userMapper;

public List<User> getUsers() {
return userMapper.getUsers();
}

public int addUser( User user ) {
return userMapper.addUser(user);
}

@Cacheable(value = "user", key = "#userName")
public List<User> getUsersByName( String userName ) {
List<User> users = userMapper.getUsersByName( userName );
System.out.println( "从数据库读取,而非读取缓存!" );
return users;
}
}

控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class UserController {
@Autowired
private UserService userService;

@Autowired
CacheManager cacheManager;

@RequestMapping( value = "/getusersbyname", method = RequestMethod.POST)
public List<User> geUsersByName(@RequestBody User user ) {
System.out.println( "-------------------------------------------" );
System.out.println("call /getusersbyname");
System.out.println(cacheManager.toString());
List<User> users = userService.getUsersByName( user.getUserName() );
return users;
}
}

控制层使用这个方法去调用,以便用于测试。

最后还有在主类上加上一个注解:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableCaching
@MapperScan("com.example.guavacache.mapper")
public class GuavacacheApplication {

public static void main(String[] args) {
SpringApplication.run(GuavacacheApplication.class, args);
}

这样子就完成了。

测试

我们可以打开这个PostMan,使用 localhost:8888/getusersbyname 发起POST请求,就可以测试出结果了。

但由于比较简单,这里就不放图了。