zoukankan      html  css  js  c++  java
  • 如何保证缓存与数据库双写时的数据一致性

      在做系统优化时,想到了将数据进行分级存储的思路。因为在系统中会存在一些数据,有些数据的实时性要求不高,比如一些配置信息。基本上配置了很久才会变一次。而有一些数据实时性要求非常高,比如订单和流水的数据。所以这里根据数据要求实时性不同将数据分为三级。

      第1级:订单数据和支付流水数据;这两块数据对实时性和精确性要求很高,所以不添加任何缓存,读写操作将直接操作数据库。

      第2级:用户相关数据;这些数据和用户相关,具有读多写少的特征,所以我们使用redis进行缓存。

      第3级:支付配置信息;这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以我们使用本地内存进行缓存。

      但是只要使用到缓存,无论是本地内存做缓存还是使用 redis 做缓存,那么就会存在数据同步的问题,因为配置信息缓存在内存中,而内存时无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题。接下来就讨论一下关于保证缓存和数据库双写时的数据一致性。

      解决方案

      那么我们这里列出来所有策略,并且讨论他们优劣性。

      先更新数据库,后更新缓存

      先更新数据库,后删除缓存

      先更新缓存,后更新数据库

      先删除缓存,后更新数据库

      先更新数据库,后更新缓存

      这种场景一般是没有人使用的,主要原因是在更新缓存那一步,为什么呢?因为有的业务需求缓存中存在的值并不是直接从数据库中查出来的,有的是需要经过一系列计算来的缓存值,那么这时候后你要更新缓存的话其实代价是很高的。如果此时有大量的对数据库进行写数据的请求,但是读请求并不多,那么此时如果每次写请求都更新一下缓存,那么性能损耗是非常大的。

      举个例子比如在数据库中有一个值为 1 的值,此时我们有 10 个请求对其每次加一的操作,但是这期间并没有读操作进来,如果用了先更新数据库的办法,那么此时就会有十个请求对缓存进行更新,会有大量的冷数据产生,如果我们不更新缓存而是删除缓存,那么在有读请求来的时候那么就会只更新缓存一次。

      先更新缓存,后更新数据库

      这一种情况应该不需要我们考虑了吧,和第一种情况是一样的。

      先删除缓存,后更新数据库

      该方案也会出问题,具体出现的原因如下。

      

    先删除缓存,后更新数据库

      此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

      请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作

      此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中

      但是此时请求 A 并没有更新成功,或者事务还未提交

      那么这时候就会产生数据库和 Redis 数据不一致的问题。如何解决呢?其实最简单的解决办法就是延时双删的策略。

      

    延时双删

      但是上述的保证事务提交完以后再进行删除缓存还有一个问题,就是如果你使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。

      

    主从同步时间差

      此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

      请求 A 更新操作,删除了 Redis

      请求主库进行更新操作,主库与从库进行同步数据的操作

      请 B 查询操作,发现 Redis 中没有数据

      去从库中拿去数据

      此时同步数据还未完成,拿到的数据是旧数据

      此时的解决办法就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。

      

    从主库中拿数据

      先更新数据库,后删除缓存

      问题:这一种情况也会出现问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。

      

    先更新数据库,后删除缓存

      此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑用语言描述如下:

      郑州男妇科医院排行榜:http://news.39.net/ylzx/zztjyy/

      郑州同济不孕不育医院:http://yyk.39.net/zz3/zonghe/fc964.html

      请求 A 先对数据库进行更新操作

      在对 Redis 进行删除操作的时候发现报错,删除失败

      此时将Redis 的 key 作为消息体发送到消息队列中

      系统接收到消息队列发送的消息后再次对 Redis 进行删除操作

      但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。

      

    利用订阅 binlog 删除缓存

      总结

      每种方案各有利弊,比如在第二种先删除缓存,后更新数据库这个方案我们最后讨论了要更新 Redis 的时候强制走主库查询就能解决问题,那么这样的操作会对业务代码进行大量的侵入,但是不需要增加的系统,不需要增加整体的服务的复杂度。最后一种方案我们最后讨论了利用订阅 binlog 日志进行搭建独立系统操作 Redis,这样的缺点其实就是增加了系统复杂度。其实每一次的选择都需要我们对于我们的业务进行评估来选择,没有一种技术是对于所有业务都通用的。没有最好的,只有最适合我们的。

  • 相关阅读:
    高斯消元学习
    HDU 4596 Yet another end of the world(解一阶不定方程)
    Codeforces Round #318 div2
    HDU 4463 Outlets(一条边固定的最小生成树)
    HDU 4458 Shoot the Airplane(计算几何 判断点是否在n边形内)
    HDU 4112 Break the Chocolate(简单的数学推导)
    HDU 4111 Alice and Bob (博弈)
    POJ 2481 Cows(线段树单点更新)
    HDU 4288 Coder(STL水过)
    zoj 2563 Long Dominoes
  • 原文地址:https://www.cnblogs.com/sushine1/p/13336111.html
Copyright © 2011-2022 走看看