yinshuang

  • 个人主页
所有文章 友链 关于我

这似乎是首纯音乐,请尽情的欣赏它吧!

yinshuang

  • 个人主页

阿里缓存框架JetCache实践

阅读数:547次 2023-08-12
字数统计: 4.5k字   |   阅读时长≈ 24分

前言

       在项目开发中,为提升系统性能,减少数据库服务器压力,通常会用到缓存。而合理的使本地内存缓存和远程缓存可以降低Cache Server的压力以及我们提供的服务的响应时间。本文将介绍如何使用 JetCache 框架简单优雅地实现多级缓存。

一、JetCache简介

JetCache 是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache 提供了比 SpringCache 更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。
当前有四个实现, RedisCache 、 TairCache (此部分未在github开源)、 CaffeineCache (in memory) 和一个简易的 LinkedHashMapCache (in memory),要添加新的实现也是非常简单的。
全部特性:

  • 通过统一的API访问Cache系统
  • 通过注解实现声明式的方法缓存,支持TTL和两级缓存
  • 通过注解创建并配置Cache实例
  • 针对所有Cache实例和方法缓存的自动统计
  • Key的生成策略和Value的序列化策略是可以配置的
  • 分布式缓存自动刷新,分布式锁 (2.2+)
  • 异步Cache API (2.2+,使用Redis的lettuce客户端时)
  • Spring Boot支持

二、JetCache用法

1.Method cache

1.缓存基本用法:声明方法上加上 @Cached、 @CacheUpdate 、 @CacheInvalidate 注解

  • name 指定缓存的唯一名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。
  • key 使用SpEL指定key,如果没有指定会根据所有参数自动生成。
  • value 使用SpEL指定value,注意REMOTE类型缓存的实体对象需要序列化(User implements Serializable),否则无法缓存到REMOTE。
  • expire 超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大。
  • cacheType 缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存。
1
2
3
4
5
6
7
8
9
10
public interface UserService {
@Cached(name="userCache-", key="#userId", expire = 3600)
User getUserById(long userId);

@CacheUpdate(name="userCache-", key="#user.userId", value="#user")
void updateUser(User user);

@CacheInvalidate(name="userCache-", key="#userId")
void deleteUser(long userId);
}

2.自动刷新和加载保护缓存: 声明方法上加上 @CacheRefresh 和 @CachePenetrationProtect 注解
自动刷新和加载保护是JetCache的大杀器,对于加载开销比较大的对象,为了防止缓存未命中时的高并发访问打爆数据库

  • refresh 刷新间隔
  • timeUnit 时间单位
  • stopRefreshAfterLastAccess 指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新
    1
    2
    3
    4
    5
    6
    public interface SummaryService{
    @Cached(expire = 3600, cacheType = CacheType.REMOTE)
    @CacheRefresh(refresh = 1800, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
    @CachePenetrationProtect
    BigDecimal summaryOfToday(long categoryId);
    }
    cacheType 为REMOTE或者BOTH的时候,刷新行为是全局唯一的,也就是说,即使应用服务器是一个集群,也不会出现多个服务器同时去刷新一个key的情况。 @CachePenetrationProtect 注解保证当缓存未命中的时候,一个JVM里面只有一个线程去执行方法,其它线程等待结果。 一个key的刷新任务,自该key首次被访问后初始化,如果该key长时间不被访问,在 stopRefreshAfterLastAccess 指定的时间后,相关的刷新任务就会被自动移除,这样就避免了浪费资源去进行没有意义的刷新。
    更多 Method cache 详细用法可以参考MethodCache文档

2.Cache API

加在方法上的注解毕竟不能提供最灵活的控制,所以JetCache提供了Cache API,使用起来就像Map一样:
1.先使用CacheManager创建Cache实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Autowired
private CacheManager cacheManager;
private Cache<String, UserDO> userCache;

@PostConstruct
public void init() {
QuickConfig qc = QuickConfig.newBuilder("userCache")
.expire(Duration.ofSeconds(100))
.cacheType(CacheType.BOTH) // two level cache
.refreshPolicy(RefreshPolicy.newPolicy(60, TimeUnit.SECONDS)) // refresh policy
.localLimit(50)
.syncLocal(true) // invalidate local cache in all jvm process after update
.build();
userCache = cacheManager.getOrCreateCache(qc);
}

2.使用 Cache API

1
2
3
4
5
UserDO user = userCache.get(12345L);
userCache.put(12345L, loadUserFromDataBase(12345L));
userCache.remove(12345L);

userCache.computeIfAbsent(1234567L, (key) -> loadUserFromDataBase(1234567L));

更多 Cache API 详细用法可以参考Cache API文档

3.Advanced API

1.Cache接口支持异步:

1
2
3
4
5
6
7
CacheGetResult r = cache.GET(userId);
CompletionStage<ResultData> future = r.future();
future.thenRun(() -> {
if(r.isSuccess()){
System.out.println(r.getValue());
}
});

2.实现不严格的分布式锁:

1
cache.tryLockAndRun("key", 60, TimeUnit.SECONDS, () -> heavyDatabaseOperation());

更多 Advanced API 详细用法可以参考Advanced API文档


三、JetCache实践

代码实践主要用了 @Cache 注解和自定义 CacheManager 的两种方式实现缓存

1.Maven pom

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xxx</groupId>
<artifactId>mp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mp</name>
<description>Demo project for SpringBoot abd MybatisPlus</description>

<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.5.2</spring-boot.version>
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<lombok.version>1.18.28</lombok.version>
<jetcache.version>2.7.3</jetcache.version>
<jedis.version>4.4.3</jedis.version>
<fastjosn2.verion>2.0.37</fastjosn2.verion>
</properties>

<dependencies>
<!-- 基础框架依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
</dependency>

<!-- jetcache依赖-->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>${jetcache.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjosn2.verion}</version>
</dependency>

<!-- 持久层相关依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

1.Entity

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
47
48
49
50
51
52
53
54
55
package com.xxx.mp.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
* @author yinshuang
* @date 2019/9/3 09:26:10
* @description User 实体类
*/
@Data
public class User implements Serializable {

private static final long serialVersionUID = 4303464655387421222L;

/**
* 主键id
*/
private Long id;

/**
* 姓名
*/
private String name;

/**
* 年龄
*/
private Integer age;

/**
* 邮箱
*/
private String email;

/**
* 管理人id
*/
private Long managerId;

/**
* 创建时间
*/
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;

/**
* 更新时间
*/
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

2.Controller

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.xxx.mp.controller;

import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CacheUpdate;
import com.alicp.jetcache.anno.Cached;
import com.xxx.mp.entity.User;
import com.xxx.mp.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
* @author yinshuang
* @date 2023/8/8 10:35
* @description UserController
*/
@Slf4j
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@GetMapping("/get/local/{id}")
@Cached(name="userCache:", key = "#id", expire = 3600, cacheType = CacheType.LOCAL)
public User getLocal(@PathVariable("id") Long userId){
log.info("数据库查询用户: userId=[{}]", userId);
return userService.getById(userId);
}

@GetMapping("/get/remote/{id}")
@Cached(name="userCache:", key = "#id", expire = 3600, cacheType = CacheType.REMOTE)
public User getRemote(@PathVariable("id") Long userId){
log.info("数据库查询用户: userId=[{}]", userId);
return userService.getById(userId);
}

@GetMapping("/get/both/{id}")
@Cached(name="userCache:", key = "#id", expire = 3600, cacheType = CacheType.BOTH)
public User getBoth(@PathVariable("id") Long userId){
log.info("数据库查询用户: userId=[{}]", userId);
return userService.getById(userId);
}

@PostMapping("/update")
@CacheUpdate(name="userCache:", key="#user.id", value="#user")
public boolean cacheUpdateUser(@RequestBody User user) {
log.info("数据库更新用户: userId=[{}]", user.getId());
return userService.updateById(user);
}

@DeleteMapping("/delete/{id}")
@CacheInvalidate(name="userCache:", key="#userId")
public boolean cacheDeleteUser(@PathVariable("id") Long userId) {
log.info("数据库删除用户: userId=[{}]", userId);
return userService.removeById(userId);
}

@GetMapping("/cachemanager/get/{id}")
public User getUser(@PathVariable("id") Long userId){
return userService.getUserById(userId);
}

@PostMapping("/cachemanager/update")
public boolean updateUser(@RequestBody User user) {
return userService.updateUserById(user);
}

@DeleteMapping("/cachemanager/delete/{id}")
public boolean deleteUser(@PathVariable("id") Long userId) {
return userService.deleteUserById(userId);
}
}

3.Service

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
package com.xxx.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxx.mp.entity.User;

/**
* @author yinshuang
* @date 2019/9/5 15:02:22
* @description service 接口类
*/
public interface UserService extends IService<User> {

/**
* 根据用户id查询用户信息
* @param userId 用户id
* @return User
*/
User getUserById(Long userId);

/**
* 根据用户id更新用户信息
*
* @param user 用户信息
* @return boolean
*/
boolean updateUserById(User user);

/**
* 根据用户id删除用户信息
* @param userId 用户id
* @return boolean
*/
boolean deleteUserById(Long userId);
}
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.xxx.mp.service;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxx.mp.entity.User;
import com.xxx.mp.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.Duration;

/**
* @author yinshuang
* @date 2019/9/5 15:03:53
* @description service 实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

private final CacheManager cacheManager;
private Cache<Long, User> userCache;

@PostConstruct
public void init() {
QuickConfig qc = QuickConfig.newBuilder("userCache:")
.expire(Duration.ofSeconds(3600))
.cacheType(CacheType.BOTH) // two level cache
.syncLocal(true) // invalidate local cache in all jvm process after update
.build();
userCache = cacheManager.getOrCreateCache(qc);
}

@Override
public User getUserById(Long userId) {
if (userCache.get(userId) != null) {
log.info("缓存查询用户: userId=[{}]", userId);
return userCache.get(userId);
}
log.info("数据库查询用户: userId=[{}]", userId);
User user = getById(userId);
userCache.put(userId, user);
return user;
}

@Override
public boolean updateUserById(User user) {
log.info("缓存更新用户: userId=[{}]", user.getId());
userCache.put(user.getId(), user);
return updateById(user);
}

@Override
public boolean deleteUserById(Long userId) {
log.info("缓存删除用户: userId=[{}]", userId);
userCache.remove(userId);
return removeById(userId);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.xxx.mp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxx.mp.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
* @author yinshuang
* @date 2019/9/3 09:27
* @description UserMapper 接口类
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

4.Config

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.xxx.mp;

import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
* @author yinshuang
* @date 2019/9/3 09:20:49
* @description SpringBoot 启动类
*/
@EnableMethodCache(basePackages = "com.xxx.mp")
@EnableConfigurationProperties
@SpringBootApplication
public class MpApplication {

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

}

application.yml配置

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 服务器配置
server:
port: 7073 #端口
servlet:
context-path: /api #接口前缀
tomcat:
uri-encoding: UTF-8 #编码格式
basedir: ./tmp #设置tomcat目录防止linux清除默认tomcat缓存

spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ********
username: ********
password: ********

# redis配置
redis:
host: ********
port: 6379
password: ********
timeout: 5000
jedis:
pool:
max-active: 10
min-idle: 0
max-wait: 100
max-idle: 10

# jetcahe配置
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
local:
default:
type: caffeine #other choose:caffeine/linkedhashmap
keyConvertor: fastjson2 #other choose:fastjson/jackson
limit: 100
remote:
default:
type: redis
keyConvertor: fastjson2 #other choose:fastjson/jackson
broadcastChannel: projectA
valueEncoder: java
valueDecoder: java
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
host: ${spring.redis.host}
port: ${spring.redis.port}
password: ${spring.redis.password}

# 日志打印配置
logging:
pattern:
console: '%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n'
level:
com.xxx.mp.mapper: trace
com.xxx.mp: debug

5.sql-script

数据库脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#创建用户表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
`name` VARCHAR(30) DEFAULT NULL COMMENT '姓名',
`age` INT(11) DEFAULT NULL COMMENT '年龄',
`email` VARCHAR(30) DEFAULT NULL COMMENT '邮箱',
`manager_id` BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NULL COMMENT '更新时间',
CONSTRAINT `manager_fk` FOREIGN KEY (`manager_id`)
REFERENCES user (`id`)
) ENGINE = INNODB CHARSET = utf8mb4;

#初始数据:
TRUNCATE TABLE `user`;
INSERT INTO `user` (id, name, age, email, manager_id, create_time, update_time)
VALUES (1087983456837465371, '大boss', 40, 'boss@163.com', null, '2019-09-03 10:57:57', null),
(1088245068394589758, '王天风', 25, 'wtf@163.com', 1087983456837465371, '2019-09-03 10:57:57', null),
(1088250896984663486, '李艺伟', 28, 'lyw@163..com', 1088245068394589758, '2019-09-03 10:58:34', null),
(1094590594354875565, '张雨绮', 31, 'zyq@163..com', 1088245068394589758, '2019-09-03 10:59:25', null),
(1094592298347592844, '刘红雨', 32, 'lyh@163..com', 1088245068394589758, '2019-09-03 11:02:17', null);

6.UnitTest

接口单元测试

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package com.xxx.mp;

import com.alibaba.fastjson2.JSON;
import com.xxx.mp.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import javax.annotation.Resource;

import java.time.LocalDateTime;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* @author yinshuang
* @date 2023/8/12 10:51
* @description UserControllerTest
*/
@Slf4j
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MpApplication.class)
public class UserControllerTest {

@Resource
private MockMvc mockMvc;

@Test
@DisplayName("local cache get user: 本地缓存配置的caffeine缓存")
public void testGetLocalUser() throws Exception {
String userId = "1087983456837465371";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user/get/local/" + userId);
// 调用接口
String result1 = doAndGetResult(requestBuilder, null);
String result2 = doAndGetResult(requestBuilder, null);
Assertions.assertEquals(result1, result2);
}

@Test
@DisplayName("remote cache get user: remote配置的redis缓存")
public void testGetRemoteUser() throws Exception {
String userId = "1088250896984663486";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user/get/remote/" + userId);
// 调用接口
String result1 = doAndGetResult(requestBuilder, null);
String result2 = doAndGetResult(requestBuilder, null);
Assertions.assertEquals(result1, result2);
}

@Test
@DisplayName("both cache get user: local和remote组成的二级缓存, L1-caffeine缓存, L2-redis缓存")
public void testGetBothUser() throws Exception {
String userId = "1088245068394589758";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user/get/both/" + userId);
// 调用接口
String result1 = doAndGetResult(requestBuilder, null);
String result2 = doAndGetResult(requestBuilder, null);
Assertions.assertEquals(result1, result2);
}

@Test
@DisplayName("cache update user")
public void testUpdateUser() throws Exception {
User user = new User();
user.setId(1087983456837465371L);
user.setName("大boss");
user.setAge(25);
user.setEmail("boss@163.com");
user.setManagerId(null);
user.setCreateTime(LocalDateTime.parse("2019-09-03T10:57:57"));
user.setUpdateTime(LocalDateTime.now());
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/user/update");
// 调用接口
doAndGetResult(requestBuilder, user);
}

@Test
@DisplayName("cache delete invalid user")
public void testDeleteUser() throws Exception {
String userId = "1088245068394589758";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/user/delete/" + userId);
// 调用接口
doAndGetResult(requestBuilder, null);
}

@Test
@DisplayName("cache manager get user: 配置cache manager")
public void testCacheManagerGetUser() throws Exception {
String userId = "1087983456837465371";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user/cachemanager/get/" + userId);
// 调用接口
String result1 = doAndGetResult(requestBuilder, null);
String result2 = doAndGetResult(requestBuilder, null);
Assertions.assertEquals(result1, result2);
}

@Test
@DisplayName("cache manager update user: 配置cache manager")
public void testCacheManagerUpdateUser() throws Exception {
User user = new User();
user.setId(1087983456837465371L);
user.setName("大boss");
user.setAge(25);
user.setEmail("boss@163.com");
user.setManagerId(null);
user.setCreateTime(LocalDateTime.parse("2019-09-03T10:57:57"));
user.setUpdateTime(LocalDateTime.now());
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/user/update");
// 调用接口
doAndGetResult(requestBuilder, user);
}

@Test
@DisplayName("cache manager delete invalid user: 配置cache manager")
public void testCacheManagerDeleteUser() throws Exception {
String userId = "1087983456837465371";
// http请求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/user/delete/" + userId);
// 调用接口
doAndGetResult(requestBuilder, null);
}

private String doAndGetResult(MockHttpServletRequestBuilder requestBuilder, Object requestContent) throws Exception {
if (requestContent != null) {
requestBuilder.contentType(MediaType.APPLICATION_JSON_VALUE).content(JSON.toJSONString(requestContent));
}
ResultActions resultActions = mockMvc.perform(requestBuilder);
// 响应结果设置字符集,防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
resultActions.andDo(print()).andExpect(status().isOk());
return resultActions.andReturn().getResponse().getContentAsString();
}
}

7.http-request

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
### 查询用户信息-local
GET {{baseUrl}}/user/get/local/1087983456837465371

### 查询用户信息-remote
GET {{baseUrl}}/user/get/remote/1088250896984663486

### 查询用户信息-both
GET {{baseUrl}}/user/get/both/1088245068394589758

### 删除用户信息
DELETE {{baseUrl}}/user/delete/1088250896984663486

### 更新用户信息
POST {{baseUrl}}/user/update
Content-Type: application/json;charset=UTF-8

{
"id": 1088250896984663486,
"name": "李艺伟",
"age": 25,
"email": "lyw@163.com",
"managerId": 1088245068394589758,
"createTime": "2019-09-03 10:58:34",
"updateTime": "2023-08-04 15:57:03"
}

### 查询用户信息-cachemanager
GET {{baseUrl}}/user/get/cachemanager/1088250896984663486

### 更新用户信息-cachemanager
POST {{baseUrl}}/user/cachemanager/update
Content-Type: application/json;charset=UTF-8

{
"id": 1088250896984663486,
"name": "李艺伟",
"age": 25,
"email": "lyw@163.com",
"managerId": 1088245068394589758,
"createTime": "2019-09-03 10:58:34",
"updateTime": "2023-08-04 15:57:03"
}

### 删除用户信息-cachemanager
DELETE {{baseUrl}}/user/cachemanager/delete/1088250896984663486

参考文献

1.缓存之王Caffeine Cache,性能比Guava更强
2.阿里巴巴开源的通用缓存访问框架JetCache介绍
3.jetcache:阿里这款多级缓存框架一定要掌握

赏

谢谢你请我吃糖果

微信
  • 本文作者: yinshuang
  • 本文链接: https://yinshuang007.github.io/2023/08/12/阿里缓存框架Jetcache实践/
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
  • JetCache
  • Caffeine
  • Redis

扫一扫,分享到微信

阿里TransmittableThreadLocal实践
工厂+策略+模板模式消除代码中的if else
Issue Page
Error: Not Found Project
登录 码云
支持Markdown的样式
Powered by Giteement
© 2021-2025 yinshuang
GitHub:hexo-theme-yilia-plus by Litten
本站总访问量548次 | 本站访客数427人
  • 所有文章
  • 友链
  • 关于我

tag:

  • BitoAI
  • AWS CodeWhisperer
  • CodeGeeX
  • MCP
  • Fastmcp
  • LangChain
  • CherryStdio
  • OpenMCP
  • EasyPoi
  • word模板导出
  • Arthus
  • Hutool
  • Jenkins
  • Pipline
  • 企业微信机器人
  • Mapstruct Plus
  • Gitlab webhook
  • MinerU
  • DeepDoc
  • PDF
  • Mysql-json
  • ApplicationRunner
  • Java函数式编程
  • uv
  • python
  • pip
  • conda
  • poi-tl
  • Hutool-StopWatch
  • 工厂模式
  • 策略模式
  • 模板模式
  • 设计模式
  • ThreadLocal
  • TransmittableThreadLocal
  • JetCache
  • Caffeine
  • Redis

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • PDF解析神器MinerU本地部署

    2025-06-22

    #MinerU#DeepDoc#PDF

  • Fastmcp框架快速搭建MCP服务

    2025-06-21

    #MCP#Fastmcp#LangChain#CherryStdio#OpenMCP

  • poi-tl模板引擎生成word统计图实践

    2025-05-10

    #poi-tl

  • UV快速入门

    2025-04-29

    #uv#python#pip#conda

  • EasyPoi实现word模板导出

    2024-01-25

    #EasyPoi#word模板导出

  • 阿里TransmittableThreadLocal实践

    2023-11-20

    #ThreadLocal#TransmittableThreadLocal

  • 阿里缓存框架JetCache实践

    2023-08-12

    #JetCache#Caffeine#Redis

  • 工厂+策略+模板模式消除代码中的if else

    2023-08-01

    #工厂模式#策略模式#模板模式#设计模式

  • Java类型转换框架MapstructPlus使用分享

    2023-07-19

    #Mapstruct Plus

  • AI编程辅助工具分享

    2023-07-09

    #BitoAI#AWS CodeWhisperer#CodeGeeX

  • Hutool常用工具类整理

    2023-07-04

    #Hutool

  • SpringBoot中使用mysql8的json类型存储json数据实现CRUD操作

    2023-06-11

    #Mysql-json

  • SpringBoot启动常用的初始化加载数据方法

    2023-05-31

    #ApplicationRunner

  • Jenkins流水线任务配置(企业微信机器人通知)

    2023-02-25

    #Jenkins#Pipline#企业微信机器人

  • Jenkins联和Gitlab配置webhook实现push触发自动化部署

    2023-02-20

    #Jenkins#Gitlab webhook

  • 任务耗时统计工具类分享

    2023-02-18

    #Hutool-StopWatch

  • Java应用诊断利器-Arthus

    2023-02-12

    #Arthus

  • TreeUtil组装树工具类分享

    2023-02-05

    #Java函数式编程

  • 个人博客
  • GitHub
  • 码云
主要涉及技术:
Java后端开发


联系QQ:875038467

很惭愧

只做了一点微小的工作
谢谢大家