能坚持别人不能坚持的,才能拥有别人不能拥有的。
文章首发于左上角公众号,同步到博客园会延迟一到两天。
关注编程大道公众号,让我们一同坚持心中所想,一起成长!!
Redis是企业级系统高并发、高可用架构中非常重要的一个环节。Redis主要解决了关系型数据库并发量低的问题,有助于缓解关系型数据库在高并发场景下的压力,提高系统的吞吐量(具体Redis是如何提高系统的性能、吞吐量,后面会专门讲)。
而我们在Redis的实际使用过程中,难免会遇到缓存与数据库双写时数据不一致的问题,这也是我们必须要考虑的问题。如果还有同学不了解这个问题,可以搬小板凳来听听啦。
一、数据库+缓存双写不一致问题引入要讲数据库+缓存双写不一致的问题,就需要先讲一下这个问题是怎么发生的。我们选择电商系统中要求数据实时性较高的库存服务来举例讲讲这个问题。
库存可能会修改,每次修改数据库的同时也都要去更新这个缓存数据;;每次库存的数据,在缓存中一旦过期,或者是被清理掉了,前端对库存数据的请求都会发送给库存服务,去获取相应的数据。
库存这一块,写数据库的时候,直接更新redis缓存吗?实际上不是,因为没有这么简单。这里,其实就涉及到了一个问题,数据库与缓存双写,数据不一致的问题。围绕和结合实时性较高的库存服务,把数据库与缓存双写不一致问题以及其解决方案,给大家分享一下。
二、各种级别的不一致问题及解决方案 1.最初级的缓存不一致问题及解决方案问题
如果是先修改数据库,再删除缓存的方案,会有问题,试想,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,出现数据不一致的情况。
解决思路
反过来,先删除缓存,再修改数据库。读缓存读不到,查数据库更新缓存的时候就拿到了最新的库存数据。如果删除缓存成功了,而修改数据库失败了,那么数据库中依旧是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。
2.比较复杂的数据不一致问题分析当库存数据发生了变更,我们先删除了缓存,然后要去修改数据库。
设想一下,如果这个时候修改数据库的操作还没来及完成,突然一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。
数据变更的操作完成后数据库的库存被修改成了新值,但缓存中又变成了旧数据。那么这个时候是不是还会出现缓存和数据库不一致的情况?
3.为何上亿流量高并发时会出现该问题?上述问题,只有在对一个数据在并发的进行读写的时候,才可能会出现。
其实如果并发量很低的话,特别是读并发很低,每天访问量就1万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。
但是问题是,高并发了以后,问题是很多的。如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。
怎么解决?
4.更新与读取操作进行异步串行化这里说一种解决方案。
不就是还没更新数据库的就查数据库读到旧数据吗?不就是因为读在更新前面了吗?那我就让你排队执行呗。
4.1 异步串行化我在系统内部维护n个内存队列,更新数据的时候,根据数据的唯一标识,将该操作路由之后,发送到其中一个jvm内部的内存队列中(对同一数据的请求发送到同一个队列)。读取数据的时候,如果发现数据不在缓存中,并且此时队列里有更新库存的操作,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也将发送到同一个jvm内部的内存队列中。然后每个队列对应一个工作线程,每个工作线程串行地拿到对应的操作,然后一条一条的执行。
这样的话,一个数据变更的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新的时候,如果此时一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,排在刚才更新库的操作之后,然后同步等待缓存更新完成,再读库。
4.2 读操作去重多个读库更新缓存的请求串在同一个队列中是没意义的,因此可以做过滤,如果发现队列中已经有了该数据的更新缓存的请求了,那么就不用再放进去了,直接等待前面的更新操作请求完成即可,待那个队列对应的工作线程完成了上一个操作(数据库的修改)之后,才会去执行下一个操作(读库更新缓存),此时会从数据库中读取最新的值,然后写入缓存中。