公司项目是教育方面的产品,对于课程数据使用比较频繁,用户使用的是时候对其响应速度要求较高,随着使用人数越来越多,并发越来越高,查询数据库的频率越来越高,导致接口访问速度越来越差,数据库性能达到瓶颈。
目前解决此类常用数据的方案就是使用缓存,将查看的课程数据缓存到缓存中,这样,在客户端请求数据时,如果能在缓存中命中数据,那就查询缓存,不用在去查询数据库,从而减轻数据库的压力,提高服务器的性能。但是需要注意的是,当数据发生变化的时候,缓存数据也需要同事变更,需要考虑数据库和缓存如何保证一致性?
我们首选要考虑的问题,就是如果数据更新的时候,是先更新数据库还是先更新缓存呢?
这个是属于一个理想状态想的操作,我们想象一下如果并发的情况下一定会出现缓存的数据与数据库的数据不一致的问题,举例说明一下:
A请求和B请求同事更新同一个资源,A想更新这个资源的名称为lesson1
,B想更新这个资源的名称为lesson2
。
假设A进行更新数据库,已经更新成功了讲数据库的资源名称更新成了lesson1
在更新缓存之前,B这个请求先进来更新数据库操作,将资源名称更新为lesson2
了,并且同事更新缓存为lesson2
这个时候A请求才处理更新缓存操作,将缓存更新为lesson1
了
这个时候数据库的资源名称为:lesson2
,而缓存为lesson1
下面我们画个时序图帮助理解:
其实是一样的道理,并发情况下也一样,只是跟上面的反过来,所以这里就不做赘述了。
我们换个思路看看,如果我们不去做更新,而直接删除缓存的,用户更新数据库以后直接删除缓存,这样用户查询的时候查不到缓存就直接取数据库,然后再缓存。其实这个跟上面的类似,只是这种情况在于删除缓存比更新缓存要快的多,出现并发的情况的概率会小很多,而且如果加上缓存失效时间,就更可靠了,下面继续分析。
lesson2
,并更新缓存数据为lesson2
lesson1
值数据库中。lesson2
,数据库的数据为lesson1
lesson1
,更新成功以后,删除缓存;B请求来查询数据 ,查不到,则查询数据库,查到数据,再更新缓存为lesson1
如果对缓存命中率要求较高的话就得采用更新数据库再更新缓存
的方案,如果对命令率要求不高则采用先更新数据库再删除缓存
。前面也分析了这两种方案都有缺陷,都可能在并发情况下存在缓存与数据库不一致。那么针对这两种情况做下优化。
这种方案前面我们也分析过,在两个更新请求并发执行的时候,会出现数据不一致的问题,因为更新数据库和更新缓存这两个操作是独立的,而我们又没有对操作做任何并发控制,那么当两个线程并发更新它们的话,就会因为写入顺序的不同造成数据的不一致。
这个地方我们可以在更新缓存的时候增加一个分布式锁,保证同一时间只运行一个请求更新缓存,就会不会产生并发问题了,当然引入了锁后,对于写入的性能就会带来影响。然后在此基础上给缓存添加一个过期时间,这样就算出现缓存数据库不一致的情况,也有补偿机制。
其实这种情况最好解决,我们只需要在删除缓存的时候增加重试机制,引入mq消息,如果删除失败,则进行重试,删除成功,消息消费完成,然后再对缓存增加过期时间作为补偿机制,这样就可以高枕无忧了。
这里也说明一下为什么推荐删除缓存,因为大家知道更新缓存的话,如果这个缓存的数据关联比较多,那么这个查询就会很多,就会很麻烦,如果删除缓存的,就方便多了,速度也会快很多,当然这样缓存命中率就会低很多。
存的数据关联比较多,那么这个查询就会很多,就会很麻烦,如果删除缓存的,就方便多了,速度也会快很多,当然这样缓存命中率就会低很多。