使用Redis时的数据一致性问题
在开发实践中,数据最终都是保存在关系型数据库的,例如保存到MySQL中,同时,为了提高查询效率、保护关系型数据库,通常会将某些数据从关系型数据库中读取出来,存入到Redis中,后续,将优先从Redis中读取数据!
由于在关系型数据库中和在Redis中都存入了数据,如果某个数据发生了变化,通常是修改关系型数据库中的数据,当时,如果Redis中并没有及时更新,但仍从Redis中获取数据,获取到的数据就会是不准确的!
所以,当同一个数据存储到了不同的存储位置,就可能出现数据一致性问题,即2个或多不同的存储位置中,“同样”的数据其实并不相同!
关于数据一致性问题:
-
并不是有必要及时的更新数据,即:当关系型数据库中的数据发生变化后,并不一定需要更新Redis中的数据,此时,Redis中的数据是“不准确”的,但是,对于软件的使用并没有什么影响
- 例如:购买火车票时,在列表页面中显示的各趟车次的票的余量
-
某些数据可能更新频率极低,这些数据可能一开始就没有太多的数据一致性问题,并且,可以在关系型数据库中的数据发生变化时,立刻更新Redis中的数据
- 例如:商品的类别
-
另外,并不是所有数据都适合放在Redis中的,例如访问频率极低的数据,或者,数据量特别大的数据
- 例如:用户的历史订单
关于数据一致性问题的常见解决方案:
- 即时更新:当关系型数据库的数据发生变化后,马上更新Redis中的数据
- 周期性更新:当关系型数据库的数据发生变化后,不会马上更新Redis中的数据,而是每隔一段时间更新一次
- 手动更新:当关系型数据库的数据发生变化后,不会马上更新Redis中的数据,而是由管理员明确的操作才会执行更新
使用ApplicationRunner实现缓存预热
在Spring Boot项目中,任何组件类实现ApplicationRunner接口,重写其中的run()方法,此方法会在项目启动之后自动执行。
在项目的根包下创建preload.CachePreload类,在类上添加@Component注解,并实现ApplicationRunner接口,例如:
package cn.tedu.csmall.product.preload;
@Slf4j
@Component
public class CachePreload implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.debug("开始执行CachePreload.run()");
}
}
启动项目后,可以看到,以上run()方法在项目启动完成之后自动执行了。
计划任务
在Spring Boot项目中,默认是不允许执行任何计划任务的,需要在配置类上添加@EnableScheudling注解来开启。
在Spring Boot项目中,在任何组件类中,在任何方法(公有的、void返回类型的、无参数列表的)上,添加@Scheudled注解,即可使得此方法是计划任务方法。
在@Scheduled注解上,需要配置注解参数来确定计划任务的执行周期或执行时间点。
在项目的根包下创建config.ScheduleConfiguration类,用于启用计划任务:
@Slf4j
@Configuration
@EnableScheduling
public class ScheduleConfiguration {
public ScheduleConfiguration() {
log.debug("创建配置类对象:ScheduleConfiguration");
}
}
然后,在项目的根包下创建schedule.CachSchedule类,作为计划任务类,例如:
@Slf4j
@Component
public class CacheSchedule {
// fixedRate:执行频率,以毫秒为单位
@Scheduled(fixedRate = 5 * 1000)
public void a() {
log.debug("执行了计划任务……");
}
}
按照以上配置,计划任务的执行频率就是5秒。
需要注意,通过fixedRate或fixtedDelay配置的计划任务,首次执行是项目启动时就执行了(严格来说,是在项目完成启动的前一刻开始第1次执行),所以,使用这种计划任务时,可以不必再使用ApplicationRunner来处理缓存预热!