zoukankan      html  css  js  c++  java
  • Mysql和Redis数据同步策略

    为什么对缓存只删除不更新

    不更新缓存是防止并发更新导致的数据不一致。
    所以为了降低数据不一致的概率,不应该更新缓存,而是直接将其删除,
    然后等待下次发生cache miss时再把数据库中的数据同步到缓存。

    先更新数据库还是先删除缓存?

    有两个选择:
    1. 先删除缓存,再更新数据库
    2. 先更新数据库,再删除缓存

    如果先删除缓存,有一个明显的逻辑错误:考虑两个并发操作,线程A删除缓存后,线程B读该数据时会发生Cache Miss,然后从数据库中读出该数据并同步到缓存中,此时线程A更新了数据库。
    结果导致,缓存中是老数据,数据库中是新数据,并且之后的读操作都会直接读取缓存中的脏数据。(直到key过期被删除或者被LRU策略踢出)
    如果数据库更新成功后,再删除缓存,就不会有上面这个问题。
    可能是由于数据库优先,第二种方式也被称为Cache Aside Pattern。

    Cache Aside Pattern

    cache aside在绝大多数情况下能做到数据一致性,但是在极端情况仍然存在问题。

    • 首先更新数据库(A)和删除缓存(B)不是原子操作,任何在A之后B之前的读操作,都会读到redis中的旧数据。
      正常情况下操作缓存的速度会很快,通常是毫秒级,脏数据存在的时间极端。
      但是,对超高并发的应用可能会在意这几毫秒。
    • 更新完数据库后,线程意外被kill掉(真的很不幸),由于没有删除缓存,缓存中的脏数据会一直存在。
    • 线程A读数据时cache miss,从Mysql中查询到数据,还没来得及同步到redis中,
      此时线程B更新了数据库并把Redis中的旧值删除。随后,线程A把之前查到的数据同步到了Redis。
      显然,此时redis中的是脏数据。
      通常数据库读操作比写操作快很多,所以除非线程A在同步redis前意外卡住了,否则发生上述情况的概率极低。

    虽然以上情况都有可能发生,但是发生的概率相比“先删除缓存再更新数据库”会低很多。

    Double-Delete

    前面我们讲到先删除缓存(A)、后更新数据库(B)的方案有明显的错误,任何发生在A操作和B操作之间的并发读都会造成数据的最终不一致。
    Double-Delete是一种比较笨拙的修补方案,执行过程如下:

    1.delete redis cache
    2.update database
    3.sleep(500ms)
    3.delete redis cache
    

    也很好理解,在睡眠500ms后尝试再次删除缓存中的脏数据,它通过两次删除来尽可能做到数据的最终一致。
    其实,Double-Delete在数据一致性上比Cache Aside更靠谱,但是它的代价是昂贵的,
    即使,把睡眠时间缩短到100ms,对耗时敏感的应用也不会考虑这种方案。

    Read/Write Through Pattern

    cache aside是我们自己的应用程序维护两个数据存储系统,而Read/Write Through Pattern是把同步数据的问题交给缓存系统了,应用程序不需要关心。
    Read Through是指发生cache miss时,缓存系统自动去数据库加载数据。
    Write Through是指如果cache miss,直接更新数据库,然后返回,如果cache hit,则更新缓存后,由缓存系统自动同步到数据库。
    以Redis为例,通常我们不会把数据库的数据全部缓存到redis,而是采用一定的数据精简或压缩策略,以节省缓存空间。
    就是说,让缓存系统设计出通用的缓存方案不太现实,不过根据自己的业务定制一个在项目内部通用的中间件是可行的。

    Write Behind

    Write Behind方案在更新数据时,只更新缓存,不更新数据库。而是由另外一个服务异步的把数据更新到数据库。
    逻辑上,和Linux中的write back很类似。这个设计的好处是,I/O操作很快,因为是纯内存操作。
    但是由于异步写库,可能要牺牲一些数据一致性,譬如突然宕机会丢失所有未写入数据库的内存数据。

    阿里巴巴的Canal中间件是一种相反的设计,它先更新mysql,然后通过binlog把数据自动同步到redis。
    这种方案会全量同步数据到redis,不适合只缓存热点数据的应用。

    设置缓存过期时间

    无论采用哪种策略都应该设置缓存的过期时间。
    过期时间设置太长,脏数据存在的时间就越长;
    过期时间设置太短,大量的cache miss会让查询直接进入数据库。
    所以,需要根据业务场景考虑过期时间。

    总结

    以上没有哪种方案是完美的,都无法做到强一致性。
    我们总要在性能和数据准确性之间做出妥协。
    Cache Aside Pattern适用于绝大多数的场景。

    https://www.pixelstech.net/article/1562504974-Consistency-between-Redis-Cache-and-SQL-Database
    https://coolshell.cn/articles/17416.html
    为什么不更新缓存,而是直接删除

  • 相关阅读:
    Linux查看占用内存前10的命令
    使用RestTemplate调用SpringCloud注册中心内的服务
    Eureka集群配置
    MySQL常用命令集合(偏向运维管理)
    pytest: error: unrecognized arguments报错解决
    MongoDB的安装
    MongoDB多条件分组聚合查询
    在排序数组中查找元素的第一个和最后一个位置
    搜索二维矩阵
    搜索旋转排序数组
  • 原文地址:https://www.cnblogs.com/upnote/p/13185047.html
Copyright © 2011-2022 走看看