数据库与缓存双写问题

计算机领域任何一个问题都可以通过增加一个抽象“层”来解决。

业务中为了减少热点数据不必要的db查询,往往会增加一层缓存来解决I/O性能。可是I/O多了一层也就多了一层的更新维护与容错保障,当修改db中某些数据时,往往会面临缓存更新的问题,在这里简单介绍 数据库与缓存双写问题以及在业务场景如何使用双写策略

缓存更新时机

缓存在以下情况下需要更新:

  • 不存在缓存,回源至db后添加缓存
  • 缓存超时,重复上个步骤
  • 修改db,更新缓存

缓存更新策略

  • 若不存在缓存或者缓存超时:

    1. 查询db
    2. 设置缓存
  • 若缓存存在,且需要更新db,则有多种缓存更新策略:
    1. 先更新db,然后更新缓存
    2. 先删除缓存,然后更新db
    3. 先更新db,在删除缓存

本节主要讨论更新db时如何更新缓存的问题,且暂时不考虑缓存操作失败的情况(如网络原因、redis服务不可用等)

如果业务场景中不会出现修改相同数据字段竞争的问题,那么这三种更新策略毫无疑问都可以使用。如果出现缓存竞争态的情况,那么第一种策略是最先排除的:

上图所示,如果A、B先后修改db,会出现最终缓存与db不一致的现象,导致随后至缓存超时或下次更新的时间段内使用脏数据的现象。
而且业务方需要考虑的是,是否每次更新db,都需要立即刷新缓存。如果在“写频繁,而读频率远小于写的情况下,频繁的刷新缓存是否有必要?”

第二种策略,先删除缓存再更新数据库旨在牺牲性能下尽可能降低使用脏缓存的情况,可是此种情况下仍有可能出现脏缓存的情况:

如上图,A先删除缓存,同时开始更新db;与此同时B查询缓存为空,进而查询db,由于db的读性能高于写且数据库隔离级别默认为提交读,因此B查询db的数据往往为旧数据,此后B查询完毕更新缓存,导致缓存在超时时间或者下次修改db的范围内为脏数据。
如果db底层做了读写分离的情况下,这种现象更容易出现,B查询db是读库,而A修改主库后需要一定时间的同步才能保障从库的数据最新,因此在此种情况下,缓存肯定仍是脏数据。

为了避免这种情况,A可以在更新db后延时一定间隔(往往是查询db时间+设置缓存的时间)删除缓存,尽量缩短脏缓存的时段,新的请求回源db并设置新的缓存数据。如下图所示。

第三种策略先更新数据库再删除缓存,此种策略较为安全,几乎不会出现脏缓存的情况,就算出现也是会在极不合理的情况下导致脏缓存:

如上图,缓存出现脏数据的前提是第2步骤耗时大于第3、4步骤,即读耗时大于写耗时,这几乎不可能发生。就算发生,也可以通过A再次延迟删除缓存(两次删除)解决。

缓存操作问题

在上一节中提到的所有缓存更新策略都是在暂时不考虑缓存操作失败的情况(如网络原因、redis服务不可用等)前提下讨论的,如果缓存操作失败,则必须通过业务代码重试、消息队列或者设置缓存超时解决。

业务代码重试,设置合理的重试次数与间隔,如果超时后缓存仍然无法操作则需要等待缓存超时或者人为介入;

消息队列则在缓存操作失败后投递对应消息,在非业务代码中进行重试;

缓存超时则是兜底方案,这是允许最长的缓存不一致的时间。

分布式事务

比较遗憾的是,在node领域还没有类似JAVA的JTA规范及其实现,JTA规范中的核心“事务管理器TM”大都由容器来实现,如常见的jboss和websphere;TM接收业务层的事务请求,同时协同参与事务的各个资源管理器RM如dbms、mq等,实现分布式事务的提交与回滚;同时也提供分布式事务在不同自治系统的传递。

分布式事务的集中解决方案有如下几种:
1. 两阶段提交
2. 三阶段提交
3. 异步确保
4. TCC
在JAVA和其他生态已经证明了,两阶段提交的低效以及无法抗住高并发且存在单点的问题;三阶段提交虽然解决了两阶段的单点和减少协调者阻塞等待参与者的问题,但仍存在数据不一致的情况,因此这两种理论上的模型其实并不符合实际业务中的场景,在工程领域需要追求的是最优化,可见理论与现实仍然有不少差距。

那么在node场景中,处理分布式事务的方式也就只剩下两种工程上的解决方案。

node中使用异步确保模型可以使用相比较简单的基于消息队列的异步确保模型(也可基于本地数据库表)。将分布式长事务切分为多个本地事务,通过保障本地事务的可靠性实现分布式长事务的最终提交。如果参与分布式事务的某个本地事务执行出错进行回滚,则通过消息队列实现业务主动方的补偿,实现最终的数据一致性。
如下图:

TCC模型相比较异步确保而言则比较重,需要开发一个TCC的TM协调各个服务参与方,同时对参与事务的各个从服务侵入性比较大,必须提供try、confirm和cancel三个接口。其中try接口预留相关资源,并确保数据一致性,confirm接口和cancel接口保证幂等性,执行或回滚try阶段预留的资源。其中,在业务中主动调用所有参与分布式事务的从服务的try接口,并汇报给TM执行情况,由TM根据try阶段的结果完成后续的执行或回滚操作,同时记录分布式事务状态传递以及各个从服务的执行阶段等信息,便于追踪。

因此用node实现分布式事务时,在没有自研TCC中间件的前提下,可根据业务特性自行扩展异步确保型方案。

nodeEE双写与分布式事务要点一二的更多相关文章

  1. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  2. EF架构~关系表插入应该写在事务里,但不应该是分布式事务

    回到目录 这个标题很有意思,关系表插入,就是说主表和外表键在插入时,可能会有同步插的情况,如在建立主表时,扩展表需要同步完成数据的初始化工作,而对于多表插入时,我们为了保证数据的一致性会针它写在事务中 ...

  3. 借读:分布式锁和双写Redis

      本帖最后由 howtodown 于 2016-10-3 16:01 编辑问题导读1.为什么会产生分布式锁?2.使用分布式锁的方法有哪些?3.本文创造的分布式锁的双写Redis框架都包含哪些内容? ...

  4. 如何实现XA式、非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  5. 非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  6. 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题

    今天来分享一下Redis几道常见的面试题: 如何解决缓存雪崩? 如何解决缓存穿透? 如何保证缓存与数据库双写时一致的问题? 一.缓存雪崩 1.1什么是缓存雪崩? 回顾一下我们为什么要用缓存(Redis ...

  7. 【分布式事务】基于RocketMQ搭建生产级消息集群?

    导读 目前很多互联网公司的系统都在朝着微服务化.分布式化系统的方向在演进,这带来了很多好处,也带来了一些棘手的问题,其中最棘手的莫过于数据一致性问题了.早期我们的软件功能都在一个进程中,数据的一致性可 ...

  8. 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务

    文章来自: https://blog.csdn.net/qq_29242877/article/details/79033287 在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据 ...

  9. 基于两阶段提交的分布式事务实现(UP-2PC)

    引言:分布式事务是分布式数据库的基础性功能,在2017年上海MySQL嘉年华(IMG)和中国数据库大会(DTCC2018)中作者都对银联UPSQL Proxy的分布式事务做了简要介绍,受限于交流形式难 ...

随机推荐

  1. box-sizing (摘录)

    //http://www.jianshu.com/p/e2eb0d8c9de6 box-sizing其它的值 content-box 描述:在宽度和高度之外绘制元素的内边距和边框. border-bo ...

  2. sleep wait yield

    sleep 暂停当前线程,允许低优先级线程获得执行机会,但并不释放对象的锁,进入不可运行状态 yield 类似sleep,但只允许同优先级有获得执行机会,同样也不会释放锁,当前线程仍是可运行状态,因此 ...

  3. RoR - More Active Record

    Active Record Scope: default_scope:   默认的检索方式 #Use unscoped to break out of the default class Hobby ...

  4. java并发请求多个接口,顺序返回

    最近有个需求,从一个api拿数据,但是api时间参数又有范围限制,因此需要自己将时间分成多段,多次请求api,并且最终返回的数据需要保持原有的顺序 代码如下: package com.test001. ...

  5. Alibaba, I'm interested in you.

    Working for Alibaba is an aspiration for some. For other it’s the possibility of lucrative stock opt ...

  6. sudoers权限管理

    该/etc/sudoers文件的权限管理很完善,覆盖了linux中的各种命令,各种shell.编辑器等等,在此留作以后作为参考. # This file MUST be edited with the ...

  7. 支持Linux系统的加密狗

    深思数盾 https://www.sense.com.cn/ 产品:精锐5 版本:标准版.精灵版.IE版.时钟锁 快速实现高安全度的软件保护,轻松定义多种授权模式1.防止软件盗版,防止逆向工程 通过增 ...

  8. 构建RN或Weex项目时,使用Android Studio常遇到的问题

    1 . androidStudio报错No cached version available for offline mode 解决方法 原因是之前为了提高编译速度,在Gradle设置选项中开启了Of ...

  9. Python实现bp神经网络识别MNIST数据集

    title: "Python实现bp神经网络识别MNIST数据集" date: 2018-06-18T14:01:49+08:00 tags: [""] cat ...

  10. 记录心得-FastJson分层解析demo示例

    记录一下,平时用到,可速查!关键: // startArray(); 开始解析数组 // endArray(); 结束解析数组 // startObject(); 开始解析键值对 // endObje ...