0 Preface
We explained the local cache ehcache component before. In practical applications, local cache or redis is not a single use, but a combination is used to meet different business scenarios. Therefore, how to elegantly combine local cache and remote cache becomes our The problem to be studied, and this point, Alibaba's open source jetcache
components help us realize
1. Introduction to jetcache
jetcache
It is an open source Java-based caching framework developed by Ali, which supports multiple cache types: local cache, distributed cache, and multi-level cache. It can meet the caching requirements of different business scenarios.
Jetcache has the characteristics of easy to use, high performance and strong scalability. Support cache preheating, cache key prefix and other functions. Combined with spring-cache, a very elegant cache type switching can be achieved
Official website address: https://github.com/alibaba/jetcache
Official documentation: https://github.com/alibaba/jetcache/tree/master/docs/CN
2. Use of jetcache
1. Introduce dependencies. Here we use the springboot project framework and redis as a remote cache. So we introduced jetcache-starter-redis
dependencies, here my springboot version is2.6.13
If it is a non-springboot project, you can refer to the official website for configuration
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.0</version>
</dependency>
<!-- jetcache2.7.x版本需要额外添加该依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
The corresponding version description is as follows: springboot and jetcache version relationship
2. Modify the configuration file, configure the redis address and the number of threads
jetcache:
# 统计间隔,0表示不统计,开启后定期在控制台输出缓存信息
statIntervalMinutes: 15
# 是否把cacheName作为远程缓存key前缀
areaInCacheName: false
# 本地缓存配置
local:
default: # default表示全部生效,也可以指定某个cacheName
# 本地缓存类型,其他可选:caffeine/linkedhashmap
type: linkedhashmap
keyConvertor: fastjson
# 远程缓存配置
remote:
default: # default表示全部生效,也可以指定某个cacheName
type: redis
# key转换器方式n
keyConvertor: fastjson
broadcastChannel: projectA
# redis序列化方式
valueEncoder: java
valueDecoder: java
# redis线程池
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
# redis地址与端口
host: 127.0.0.1
port: 6379
For more detailed parameter configuration, please refer to the official website description: https://github.com/alibaba/jetcache/blob/master/docs/CN/Config.md
3. Add annotations to the startup class @EnableCreateCacheAnnotation
, enable caching, add @EnableMethodCache(basePackages = "com.example.jetcachedemo")
annotations, and configure the caching method scan path
4. There are three ways to use the cache:
- Method 1 (recommended) AOP mode: via
@Cached
,@CacheUpdate
,@CacheInvalidate
annotations
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("getRemote")
@Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.REMOTE)
public User getRemote(Long id){
// 直接新建用户,模拟从数据库获取数据
User user = new User();
user.setId(id);
user.setName("用户remote"+id);
user.setAge(23);
user.setSex(1);
System.out.println("第一次获取数据,未走缓存:"+id);
return user;
}
@GetMapping("getLocal")
@Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.LOCAL)
public User getLocal(Long id){
// 直接新建用户,模拟从数据库获取数据
User user = new User();
user.setId(id);
user.setName("用户local"+id);
user.setAge(23);
user.setSex(1);
System.out.println("第一次获取数据,未走缓存:"+id);
return user;
}
@GetMapping("getBoth")
@Cached(name="userCache:", key = "#id", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
public User getBoth(Long id){
// 直接新建用户,模拟从数据库获取数据
User user = new User();
user.setId(id);
user.setName("用户both"+id);
user.setAge(23);
user.setSex(1);
System.out.println("第一次获取数据,未走缓存:"+id);
return user;
}
@PostMapping("updateUser")
@CacheUpdate(name = "userCache:", key = "#user.id", value = "#user")
public Boolean updateUser(@RequestBody User user){
// TODO 更新数据库
return true;
}
@PostMapping("deleteUser")
@CacheInvalidate(name = "userCache:", key = "#id")
public Boolean deleteUser(Long id){
// TODO 从数据库删除
return true;
}
}
It should be noted here that the entity class User
must implement serialization, that is, declareSerializable
@Data
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
private Integer sex;
}
- Method 2 API mode: pass
@CreateCache
, note: the CreateCache annotation in jetcache 2.7 version is obsolete and not recommended
@RestController
@RequestMapping("user2")
public class User2Controller {
@CreateCache(name= "userCache:", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
private Cache<Long, Object> userCache;
@GetMapping("get")
public User get(Long id){
if(userCache.get(id) != null){
return (User) userCache.get(id);
}
User user = new User();
user.setId(id);
user.setName("用户both"+id);
user.setAge(23);
user.setSex(1);
userCache.put(id, user);
System.out.println("第一次获取数据,未走缓存:"+id);
return user;
}
@PostMapping("updateUser")
public Boolean updateUser(@RequestBody User user){
// TODO 更新数据库
userCache.put(user.getId(), user);
return true;
}
@PostMapping("deleteUser")
public Boolean deleteUser(Long id){
// TODO 从数据库删除
userCache.remove(id);
return true;
}
}
- Method 3 Advanced API mode: pass
CacheManager
, only version 2.7 can be used
(1) Add dependencies
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
(2) Write configuration class
@Configuration
public class JetcacheConfig {
@Autowired
private CacheManager cacheManager;
private Cache<Long, Object> userCache;
@PostConstruct
public void init(){
QuickConfig qc = QuickConfig.newBuilder("userCache:")
.expire(Duration.ofSeconds(3600))
.cacheType(CacheType.BOTH)
// 本地缓存更新后,将在所有的节点中删除缓存,以保持强一致性
.syncLocal(false)
.build();
userCache = cacheManager.getOrCreateCache(qc);
}
@Bean
public Cache<Long, Object> getUserCache(){
return userCache;
}
}
(3) Call code
@RestController
@RequestMapping("user3")
public class User3Controller {
@Autowired
JetcacheConfig jetcacheConfig;
@Autowired
private Cache<Long, Object> userCache;
@GetMapping("get")
public User get(Long id){
if(userCache.get(id) != null){
return (User) userCache.get(id);
}
User user = new User();
user.setId(id);
user.setName("用户both"+id);
user.setAge(23);
user.setSex(1);
userCache.put(id, user);
System.out.println("第一次获取数据,未走缓存:"+id);
return user;
}
@PostMapping("updateUser")
public Boolean updateUser(@RequestBody User user){
// TODO 更新数据库
userCache.put(user.getId(), user);
return true;
}
@PostMapping("deleteUser")
public Boolean deleteUser(Long id){
// TODO 从数据库删除
userCache.remove(id);
return true;
}
}
In the form of multi-level cache, the data will be obtained from the local cache first, and if it cannot be obtained locally, it will be obtained from the remote cache
5. Start redis and start the demo project
Note that if an error occurs
NoClassDefFoundError: redis/clients/util/Pool
orNoClassDefFoundError: redis/clients/jedis/UnifiedJedis
an error is reported during startup, it means that the versions of springboot and jetcache are inconsistent. For the corresponding relationship, please refer to the description in the first step above.
At the same time, if you are using version 2.7.x of jetcache, because there is a dependency on the jedis package in this version, additional Add the following dependencies, or reduce the jetcache version to below 2.6.5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
3. Test
3.1 Method 1 Test
1. Visit localhost:8088/user/getRemote?id=1
Because the remote cache is configured, the corresponding key can also be seen in redis
2. Visit localhost:8088/user/getLocal?id=1. This method is obtained from the local cache. Now only the remote cache has data. We call and find that the cache data is still obtained. This means that when we configure in the configuration file After the local cache and the remote cache are configured, the local cache and the remote cache will automatically call each other in method 1
For example, the local cache has this key, but not in redis. When accessing through remote cache, it will be obtained from redis first. If not, the local cache will be automatically obtained, but the data is still stored in the local cache and will not be synchronized to redis. This is more Flexible implementation of multi-level cache architecture
3.2 Method 2 test
1. Re-test CreateCache
the form: localhost:8088/user2/get?id=4
It is obtained normally, and there is a corresponding value in redis
And when we change the cache mode to LOCAL
, then visit localhost:8088/user2/get?id=5
@CreateCache(name= "userCache:", expire = 3600, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.LOCAL)
You will find that there is no corresponding cache in redis, and it only exists in the local cache, indicating that we have successfully specified the form of the local cache
3.3 Method 3 test
1. Call localhost:8088/user3/get?id=11
The cache setting in redis is successful!
4. Common errors
1. ClassNotFoundException: com.alibaba.fastjson.JSON
Solution: add dependencies
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
2. NoClassDefFoundError: redis/clients/jedis/UnifiedJedis
Solution:
add dependencies
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
Or reduce the jetcache version to below 2.6.5
Demo source code
https://gitee.com/wuhanxue/wu_study/tree/master/demo/jetcache-demo