• 缓存和数据库数据一致性解决方案


    一、最终一致性

    在前文缓存更新中讲到,更新缓存较好的一种方式是使用 Cache Aside 模式,即先更新数据库,再失效缓存。但是若更新数据库成功,失效缓存由于服务异常、系统异常、网络异常等原因失败,则会导致数据不一致。

    根据CAP理论,完美的一致性和可用性难以同时满足,通常会选择保证A和P,接受最终一致性。最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一致的状态。

    使用缓存最主要的原因是追求数据访问速度,而如果要追求强一致性,按照目前的技术,必然会损失访问速度这一优势,因此在需要保证数据强一致性的场景下,根本不适合使用缓存。如果选择使用缓存,则必然基于一个重要前提,即可接受弱一致性或最终一致性。

    二、补偿机制

    在操作失败后,通过保存足够的信息可进行补偿操作,实现最终一致性。常用的补偿机制:

    • 同步重试:操作失败后在当前线程中继续重试,直至成功或达到重试次数上限。缺点是可靠性不足,如缓存服务短期不可用,则重试可能不成功
    • 异步重试:将重试任务交给线程池处理,缺点是可靠性不足,如发生服务器重启等异常,则无法补偿
    • 数据库重试:将重试数据记录到数据库,由业务系统定时任务进行重试。可靠性高,但实现成本高,对业务系统侵入严重
    • 消息队列重试(推荐):将重试数据发给消息队列,由业务系统自行消费并进行重试。该方案在可靠性、系统侵入、响应时延、达到最终一致性的速度等方面可以取的折中

    消息队列重试步骤:

    1. 更新数据库
    2. 删除缓存
    3. 若失败,将需要删除的key发送给消息队列
    4. 业务系统接收到消息,根据消息内容重试删除操作

    三、延时双删

    Cache Aside模式可能会存在一种概率极低的读写时序问题:读操作缓存未命中,同时伴随一个并发写操作;读操作在写操作完成数据库更新前读取了旧数据,且在写操作删除缓存后更新了缓存,从而导致数据库中的为最新数据,而缓存中的仍为旧数据

    延时双删方案可以解决这一问题:删除缓存、更新数据库、休眠一段时间、再次删除缓存

    延时双删实现简单,但是因延迟等待会影响写请求性能,可以通过异步删除缓存来解决,典型的方案有:基于binlog 删除缓存和消费消息删除缓存

    四、异步删除缓存

    • 基于binlog异步删除缓存:通过Canal监听binlog变更,当识别到涉及缓存的数据更新操作时,将对应缓存删除
    • 消息队列异步删除缓存:Canal收到消息后,将binlog数据直接投递到MQ,从而实现多个客户端同时消费数据。删除缓存成功后,通过MQ的ACK机制确定消费成功

    该方式实现复杂,一般不会使用,绝大多数场景 Cache Aside + 自动过期 + 失败补偿 策略已能够满足要求

  • 相关阅读:
    Visual Paradigm 里什么是复合结构图?
    C++ Reference: Standard C++ Library reference: C Library: cstdio: fwrite
    安卓常见设计模式------装饰器模式(Kotlin版)
    postgresql14-用户与角色(二)
    记录Centos安装R语言环境,使用Rmd生成html
    ZYNQ_project:uart(odd,even)
    学姐突然问我键盘怎么选?原来是为了这个...
    UrlBasedCorsConfigurationSource无法转换为CorsConfigurationSource的原因
    SpringBoot SpringBoot 原理篇 1 自动配置 1.12 bean 的加载控制【注解式】
    RabbitMQ(六)仲裁队列、流式队列、异地容灾(联邦队列Federation Queue)
  • 原文地址:https://blog.csdn.net/wanger61/article/details/133274212