删除缓存还是更新缓存
我们存入缓存的数据,往往是经过一系列的计算才放如缓存的,而不是从数据库直接读取出来,放入缓存;所以更新缓存的代价往往会比较大。 例如:一分钟内可能修改一个字段100次,然后要将相关缓存的数据计算出来,还要查询其他的字段进行计算,然而这个缓存在1分钟内只被读一次,所以如果这时候更新缓存代价就会比较大,而删除删除的代价就小很多。 业界上大部分是删除缓存,而不是更新缓存
为什么缓存和数据库会不一致?
场景1:先更新数据库,再删除缓存 假设先更新数据库成功,删除缓存失败,这时候数据库和缓存就不一致了。 所以先删除缓存,再更新数据库 假设删除缓存成功,更新数据库失败,这个时候,数据是一致的。因为缓存空会去数据库读取。
场景2:先删除缓存,再更新数据库,并读取数据 我们知道删除缓存和更新数据库是两个操作,假设有这么一种情况,我删除缓存了。然后正要去更新数据库的时候但是还没更新,这个时候来了一个读取请求,并且抢到了cpu。读请求发现缓存为空,会去数据库读取,并存入缓存,,,这个时候才更新数据库,这样就会导致缓存和数据库不一致。
当然在并发很低的情况下,出现这个问题的概率会比较低,但是在并发很高的情况下,很容易就出现这个问题的。。
解决方案:删除缓存,更新数据库,读取数据异步串行化
https://zhuanlan.zhihu.com/p/77587581
异步串行化
当我们要更新数据库数据的时候将数据的唯一标识(比如修改库存,商品id/sku)放入一个jvm的内存队列中。
当我们读取请求过来的时候,如果缓存有数据,直接返回。如果没有数据,就要去读取数据库 更新缓存,这个时候也将唯一标识放在jvm的内存队列中
注意:同一个标识要路由到同一个队列,并且一个队列只能由一个线程进行消费;;这种方案类似rockermq的顺序消费。
由于队列是先进先出的,这个的话更新数据库操作(删除缓存 更新数据库)就会先于读取请求(读取数据 更新缓存)执行。
优化点:当队列中有一个读请求的时候(读取数据 更新缓存),这个没必要放入更多的读请求了到jvm内存队列中。之后的读请求可以使用不断的轮询去读取缓存,等待jvm的那个读请求更新缓存就行。。 当轮询等待的时候过长(200ms)可以直接读取数据库的数据并返回。
注意:队列中可能会堆积多个更新数据库的操作(同一数据),导致读取请求轮询超时直接读取数据库(所以要更新数据频繁的情景会怎么样)。
队列中堆积的更新操作可能是针对不同的数据项的,所以,要设当的扩大队列来分摊更新操作。
比如:如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费10ms区完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据
其实根据之前的项目经验,一般来说数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的 针对读高并发,读缓存架构的项目,一般写请求相对读来说,是非常非常少的,每秒的QPS能到几百就不错了 一秒,500的写操作,5份,每200ms,就100个写操作
单机器,20个内存队列,每个内存队列,可能就积压5个写操作,每个写操作性能测试后,一般在20ms左右就完成 那么针对每个内存队列中的数据的读请求,也就最多hang一会儿,200ms以内肯定能返回了
写QPS扩大10倍,但是经过刚才的测算,就知道,单机支撑写QPS几百没问题,那么就扩容机器,扩容10倍的机器,10台机器,每个机器20个队列,200个队列
注意:对同一个商品的更新和读取,要路由到同一台机器;; 比如,写操作路由到机器1,读操作路由到机器2,那这样还讲什么串行话,就gg了。所以要路由到同一个机器。(nginx的hash算法)
这样又会导致另一种场景,热点商品的读取频率会比较高,会导致的服务器的压力大,因为某一个热点商品的读请求,会打到同一个服务器上面。造成某台服务器压力大。。。
全量更新
我们都知道redis的性能跟存入的value的大小有很大的关系。。。当我们存入的value很大这样会导致redis的性能急剧下降。
当我们存入的redis的数据是商品详情页数据(商品基本信息,商品分类信息,商品店铺信息 )混在一起会导致redis的value很大。。。这样当我们只想修改商品的颜色这个小小的基本信息。。。就需要将分类及店铺都拿出来在set进去。。这样读取的数据都非常大。。导致性能很低
我们可以分维度存在redis 比如商品基本信息对应一个key。。商品分类信息对应一个key。。商品店铺信息对应一个key。。分成3个维护存储、、只想修改基本信息,就拿基本信息来修改。。。