Spring分布式缓存

原创
2021/06/06 22:21
阅读数 109

Spring分布式缓存

什么是分布式缓存?在实际开发场景中,往往单机应用无法满足当前的需求,需要对项目进行分布式部署,由此每个项目中的缓存都是属于自己独立服务的,并不能共享,其次当某个服务更新了缓存,其他服务并不知道,当用户请求到其他服务时,获取到的往往还是旧的数据。 这时就需要将缓存的数据放在一个统一的地方进行管理,如:redis

注解介绍

Spring为我们提供了三大注解@Cacheable@CachePut@CacheEvict可在绝大部分场景下优雅实现分布式缓存。

@EnableCaching(启用缓存)

一般注解在启动类或配置类上,表示启用缓存,使@Cacheable@CachePut@CacheEvict等注解生效。

@Cacheable(添加/获取缓存)

属性名 作用
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。
sync 是否使用异步模式进行缓存,默认false。

cache_test作为缓存名,参数id作为缓存key,如果命中缓存,直接返回结果。如果缓存不存在,就执行方法逻辑,并将方法的返回结果缓存。

@Cacheable(value = "cache_test", key = "#id")
@GetMapping("/get")
public Result<?> get(Long id) {
	return R.success(jdbcDAO.get(id));
}

@CachePut(更新缓存)

属性名 作用与描述
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。

cache_test作为缓存名,参数id作为缓存key,当方法逻辑执行完之后,将返回结果进行覆盖缓存。

@CachePut(value = "cache_test", key = "#userGroupIPO.id")
@PutMapping("/put")
public Result<?> put(@Validated UserGroupIPO userGroupIPO) {
	jdbcDAO.updateById(Convert.toJSONObject(userGroupIPO));
	return R.success(jdbcDAO.get(userGroupIPO.getId()));
}

@CacheEvict(删除缓存)

属性名 作用与描述
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定删除缓存的条件(对参数判断,满足什么条件时才删除缓存),可用SpEL表达式,例如:入参为字符userId的方法删除缓存条件设定为当入参不是user001就删除缓存,则表达式可以写为condition = "!('user001').equals(#userId)"
allEntries allEntries是布尔类型的,用来表示是否需要清除缓存中的所有元素。默认值为false,表示不需要。当指定allEntries为true时,Spring Cache将忽略指定的key,清除缓存中的所有内容。
beforeInvocation 清除操作默认是在对应方法执行成功后触发的(beforeInvocation = false),即方法如果因为抛出异常而未能成功返回时则不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间。当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

cache_test作为缓存名,参数id作为缓存key,当方法逻辑执行完之后,删除缓存。

@CacheEvict(value = "cache_test", key = "#id")
@DeleteMapping("/delete")
public Result<?> delete(@RequestParam("id") Long id) {
	jdbcDAO.deleteLogic(id);
	return R.success();
}

@CacheConfig

注解在类上面,可使@Cacheable@CachePut@CacheEvict注解无需定义重复的:cacheNames/value、keyGenerator、cacheManager、cacheResolver属性值

@CacheConfig(cacheNames = "cache_test")
@RestController
@RequestMapping("/cache")
public class CacheController {

    @Autowired
    JdbcDAO jdbcDAO;

    @Cacheable(key = "#id") // 无需重复指定 cacheNames/value 属性
    @GetMapping("/get")
    public Result<?> get(Long id) {
        return R.success(jdbcDAO.get(id));
    }

}

@Caching

用于将@Cacheable、@CachePut、@CacheEvict这三个注解所提供的能力自由组合,如查询用户时缓存不应该只放id → user,应该连同 cellphone → user、email → user一起放入,这样下次如果按照cellphone或email来查询时,也可从缓存中命中了。

@Caching(
	cacheable = {
		@Cacheable(value = "cache_test", key = "#id")
	},
	put = {
		@CachePut(value = "cache_test", key = "#result.cellphone", condition = "#result != null"),
		@CachePut(value = "cache_test", key = "#result.email", condition = "#result != null")
	}
)
public UserDO getUserDO(Long id) {
	return jdbcDAO.get(id);
}

开始使用

引入依赖

引入spring-boot-starter-data-redis依赖:

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

启用缓存

在启动类中加上@EnableCaching注解开启缓存

@EnableCaching
@SpringBootApplication
public class TestApplication {

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

}

使用缓存

@Cacheable(value = "cache_test", key = "#id")
@GetMapping("/get")
public Result<?> get(Long id) {
	System.out.println("未命中Redis缓存,使用JDBC去数据库查询数据。");
	return R.success(jdbcDAO.get(id));
}

测试

http://localhost:8080/cache/get?id=18

第一次访问-控制台打印结果:

2021-06-05 19:15:34.041  INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect    : requestIp=0:0:0:0:0:0:0:1
2021-06-05 19:15:34.041  INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect    : requestUri=/cache/get
2021-06-05 19:15:34.041  INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect    : requestMethod=GET
2021-06-05 19:15:34.041  INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect    : requestHandlerMethod=ai.yue.library.test.controller.data.redis.CacheController.get()
未命中Redis缓存,使用JDBC去数据库查询数据。
2021-06-05 19:54:11.332 DEBUG 44320 --- [nio-8080-exec-4] druid.sql.Statement                      : {conn-310002, pstmt-320001} executed.
select * from `user` where 1 = 1 and `id` = 18

第一次访问-响应数据:

{
    "code": 200,
    "msg": "成功",
    "flag": true,
    "count": null,
    "data": {
        "id": 18,
        "sortIdx": null,
        "deleteTime": 0,
        "createTime": "2018-07-18 09:10:46",
        "updateTime": "2021-06-03 14:29:41",
        "cellphone": "185****6311",
        "email": null,
        "password": "***********************************************",
        "nickname": "张三丰3",
        "sex": "男"
    }
}

第二次访问:结果与第一次相同,但未打印出查询日志,因此证明响应的结果是取的Redis缓存数据,而不是执行的JDBC查询。同时我们也确认下Redis中是否有缓存数据:

确认Redis缓存

维护缓存

上面我们介绍了如何使用@Cacheable注解添加与获取缓存,实际场景中我们还需要更新缓存@CachePut与删除缓存@CacheEvict。 开发者需要结合业务情况,在需要操作到缓存相关数据时,进行缓存数据同步,也就是更新或删除缓存,需求多变灵活运用。

扩展资料

👉示例源码

👉Redis缓存雪崩、击穿、穿透、预热、降级详解

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部