Day15-使用Redis时的数据一致性问题

229 阅读3分钟

使用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秒。

需要注意,通过fixedRatefixtedDelay配置的计划任务,首次执行是项目启动时就执行了(严格来说,是在项目完成启动的前一刻开始第1次执行),所以,使用这种计划任务时,可以不必再使用ApplicationRunner来处理缓存预热!