希望长大对我而言,是可以做更多想做的事,而不是被迫做更多不想做的事...... 首页 缓存和数据库不一致分析及解决方案及多维度化 丁D 学无止境 2019-06-03 10:08 3045已阅读 缓存和数据库不一致 数据库缓存双写一致性 摘要在高并发的场景下如果对同一个数进行操作那么可能导致缓存和数据库不一致,本文将详细分析原因并讲解其解决方案。 ###删除缓存还是更新缓存 我们存入缓存的数据,往往是经过一系列的计算才放如缓存的,而不是从数据库直接读取出来,放入缓存;所以更新缓存的代价往往会比较大。 例如:一分钟内可能修改一个字段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个维护存储、、只想修改基本信息,就拿基本信息来修改。。。 很赞哦! (8) 上一篇:数据库主从不一致 下一篇:ansible初识 目录 点击排行 Elasticsearch6.3.2之x-pack redis哨兵 2019-07-09 22:05 Redis+Twemproxy+HAProxy+Keepalived 2019-07-12 17:20 GC优化策略和相关实践案例 2019-10-10 10:54 JVM垃圾回收器 2019-10-10 10:23 标签云 Java Spring MVC Mybatis Ansible Elasticsearch Redis Hive Docker Kubernetes RocketMQ Jenkins Nginx 友情链接 郑晓博客 佛布朗斯基 凉风有信 MarkHoo's Blog 冰洛博客 南实博客 Rui | 丁D Java研发工程师 生活可以用「没办法」三个字概括。但别人的没办法是「腿长,没办法」、「长得好看,没办法」、「有才华,没办法」。而你的没办法,是真的没办法。 请作者喝咖啡