Spring Boot中的缓存支持(二)使用Redis做集中式缓存

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 上一篇介绍了在Spring Boot中如何引入缓存、缓存注解的使用、以及EhCache的整合。虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进程内的缓存框架,在集群模式下时,各应用服务器之间的缓存都是独立的,因此在不同服务器的进程间会存在缓存不一致的情况。

上一篇介绍了在Spring Boot中如何引入缓存、缓存注解的使用、以及EhCache的整合。

虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进程内的缓存框架,在集群模式下时,各应用服务器之间的缓存都是独立的,因此在不同服务器的进程间会存在缓存不一致的情况。即使EhCache提供了集群环境下的缓存同步策略,但是同步依然需要一定的时间,短暂的缓存不一致依然存在。

在一些要求高一致性(任何数据变化都能及时的被查询到)的系统和应用中,就不能再使用EhCache来解决了,这个时候使用集中式缓存是个不错的选择,因此本文将介绍如何在Spring Boot的缓存支持中使用Redis进行数据缓存。

下面以上一篇的例子作为基础进行改造,将缓存内容迁移到redis中。

准备工作

可以下载案例Chapter4-4-1,进行下面改造步骤。

先来回顾一下在此案例中,我们做了什么内容:

  • 引入了spring-data-jpaEhCache
  • 定义了User实体,包含idnameage字段
  • 使用spring-data-jpa实现了对User对象的数据访问接口UserRepository
  • 使用Cache相关注解配置了缓存
  • 单元测试,通过连续的查询和更新数据后的查询来验证缓存是否生效

开始改造

  • 删除EhCache的配置文件src/main/resources/ehcache.xml

  • pom.xml中删除EhCache的依赖,增加redis的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>
  • application.properties中增加redis配置,以本地运行为例,比如:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1

我们需要做的配置到这里就已经完成了,Spring Boot会在侦测到存在Redis的依赖并且Redis的配置是可用的情况下,使用RedisCacheManager初始化CacheManager

为此,我们可以单步运行我们的单元测试,可以观察到此时CacheManager的实例是org.springframework.data.redis.cache.RedisCacheManager,并获得下面的执行结果:

Hibernate: insert into user (age, name) values (?, ?)
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=?
第一次查询:10
第二次查询:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查询:10

可以观察到,在第一次查询的时候,执行了select语句;第二次查询没有执行select语句,说明是从缓存中获得了结果;而第三次查询,我们获得了一个错误的结果,根据我们的测试逻辑,在查询之前我们已经将age更新为20,但是我们从缓存中获取到的age还是为10。

问题思考

为什么同样的逻辑在EhCache中没有问题,但是到Redis中会出现这个问题呢?

在EhCache缓存时没有问题,主要是由于EhCache是进程内的缓存框架,第一次通过select查询出的结果被加入到EhCache缓存中,第二次查询从EhCache取出的对象与第一次查询对象实际上是同一个对象(可以在使用Chapter4-4-1工程中,观察u1==u2来看看是否是同一个对象),因此我们在更新age的时候,实际已经更新了EhCache中的缓存对象。

而Redis的缓存独立存在于我们的Spring应用之外,我们对数据库中数据做了更新操作之后,没有通知Redis去更新相应的内容,因此我们取到了缓存中未修改的数据,导致了数据库与缓存中数据的不一致。

因此我们在使用缓存的时候,要注意缓存的生命周期,利用好上一篇上提到的几个注解来做好缓存的更新、删除

进一步修改

针对上面的问题,我们只需要在更新age的时候,通过@CachePut来让数据更新操作同步到缓存中,就像下面这样:

@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {

    @Cacheable(key = "#p0")
    User findByName(String name);

    @CachePut(key = "#p0.name")
    User save(User user);

}

在redis-cli中flushdb,清空一下之前的缓存内容,再执行单元测试,可以获得下面的结果:

Hibernate: insert into user (age, name) values (?, ?)
第一次查询:10
第二次查询:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查询:20

可以看到,我们的第三次查询获得了正确的结果!同时,我们的第一次查询也不是通过select查询获得的,因为在初始化数据的时候,调用save方法时,就已经将这条数据加入了redis缓存中,因此后续的查询就直接从redis中获取了。

本文内容到此为止,主要介绍了为什么要使用Redis做缓存,以及如何在Spring Boot中使用Redis做缓存,并且通过一个小问题来帮助大家理解缓存机制,在使用过程中,一定要注意缓存生命周期的控制,防止数据不一致的情况出现。

完整示例:Chapter-4-4-2

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
22天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
38 0
|
1月前
|
缓存 NoSQL 安全
【Redis】缓存穿透
【Redis】缓存穿透
30 0
|
21天前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
42 0
|
1天前
|
安全 Java 应用服务中间件
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
4 0
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
|
3天前
|
XML Java C++
【Spring系列】Sping VS Sping Boot区别与联系
【4月更文挑战第2天】Spring系列第一课:Spring Boot 能力介绍及简单实践
28 0
【Spring系列】Sping VS Sping Boot区别与联系
|
3天前
|
缓存 NoSQL Java
使用Redis进行Java缓存策略设计
【4月更文挑战第16天】在高并发Java应用中,Redis作为缓存中间件提升性能。本文探讨如何使用Redis设计缓存策略。Redis是开源内存数据结构存储系统,支持多种数据结构。Java中常用Redis客户端有Jedis和Lettuce。缓存设计遵循一致性、失效、雪崩、穿透和预热原则。常见缓存模式包括Cache-Aside、Read-Through、Write-Through和Write-Behind。示例展示了使用Jedis实现Cache-Aside模式。优化策略包括分布式锁、缓存预热、随机过期时间、限流和降级,以应对缓存挑战。
|
12天前
|
存储 缓存 NoSQL
Java手撸一个缓存类似Redis
`LocalExpiringCache`是Java实现的一个本地缓存类,使用ConcurrentHashMap存储键值对,并通过ScheduledExecutorService定时清理过期的缓存项。类中包含`put`、`get`、`remove`等方法操作缓存,并有`clearCache`方法来清除过期的缓存条目。初始化时,会注册一个定时任务,每500毫秒检查并清理一次过期缓存。单例模式确保了类的唯一实例。
11 0
|
1月前
|
缓存 NoSQL Java
spring cache整合redis实现springboot项目中的缓存功能
spring cache整合redis实现springboot项目中的缓存功能
45 1
|
1月前
|
缓存 NoSQL 数据库
[Redis]——数据一致性,先操作数据库,还是先更新缓存?
[Redis]——数据一致性,先操作数据库,还是先更新缓存?
|
1月前
|
存储 NoSQL Java
[Redis]——Spring整合Redis(SpringDataRedis)
[Redis]——Spring整合Redis(SpringDataRedis)