Databases, Software Design
     

数据同步的最终一致性、准确性和可重复性

构建分布式、可扩展的系统非常复杂,而且由于 IoT 和移动设备数量的不断增加,因此更加常见。我经常看到的一个问题就是开发人员试图将分布式系统视为单一系统。具体到,保留模式(如 ACID)的开发人员。

ACID 是原子性、一致性、隔离性和持久性的简短,是关系数据库 (RDMS) 的核心支柱。对于许多应用程序,ACID 描述的行为以及 RDMS 强制实施的行为正是所需的行为。没有人想要像肮脏的读物这样的种族条件。但是,您阅读本文是因为您可能对分布式系统感兴趣 – 简言之,您不是构建典型的应用程序,当然也不是 Codd 等 20 世纪 60 年代所设想的应用程序类型。al. 为现代 RDMS 奠定了基础。

picture of Saturn, God of time
也许土星,时间之神,知道一些关于最终一致性的事情?

经典数据库设计的大多数元素都围绕着一个中央权威(通常是RDMS本身)的门卫的概念。当所有客户端都可以保持与中央机构的连接(例如,数据库连接)时,只要它希望执行写入,这就可以很好地工作。只要您能够满足此要求,并提供可以随着客户端数量和使用率的增长而扩展此单一瓶颈,那么这不是一个坏的解决方案。事实上,这是一个很好的解决方案,因为 ACID 的刚性存在是有原因的,RDMS 可以抵御所有类型的不良数据方案。

显然,您没有构建”始终连接”的东西,因此无法实现严格的 ACIDity。然后呢?

为了回答这个问题,让我们退后一步,问为什么我们试图完成什么,并扩展我们关心什么。对于许多系统,这可以通过简单的语句来表述:

“我希望确保正确处理任何更新,如果发生冲突,应用程序会执行正确的操作。”

好了,够了这些要求清楚地定义了用户的观点,但显然留有一点回旋余地来解释 – 我们将运行这与它,并评估其出现的歧义。

要求 1:”确保正确处理任何更新。

假设”正确”表示”当更新到达时,处理它就像客户端始终连接一样”,那么我们可以非常轻松地完成此操作。只需让脱机客户端对更新请求进行排队,当他们达到联机状态时,只需将它们传输到服务器,按时间顺序遍数它们,处理每个请求。如果没有冲突,这很简单 – 虽然,我们将看到,有问题所在。

要求2:”如果发生冲突,做正确的事”

正如你可能已经推测的,这就是事情变得复杂的地方。冲突有多种形式,因此我们将反过来定义每一种形式,然后讨论所有这些问题的一般解决方案。

多个更新

如果多个客户端尝试更新同一记录,则执行哪些工作?如果他们更新同一字段,或者为同一记录更新不同的字段,那将怎么办?

派生/计算字段

许多应用程序放弃严格的数据库架构规范化进行性能优化,有时这采用计算字段的形式。子记录的总和、对象的状态(例如状态机状态)等。当冲突更新(见上文)也对计算字段产生影响时会发生什么?

删除

理想情况下,您应该避免硬删除,并实现某种形式的逻辑删除,但这些模式如何阻止冲突?

我们如何继续?

在上述冲突类型的细分中,让我们回顾一下如何在断开连接的系统中克服这一问题。

值得庆幸的是,由于每种冲突类型的相似性,因此存在一个简单的解决办法,而且,对于这一点,所有冲突都是一种。所有冲突本质上都是计时错误。事情 A 应该先于 B 的事情, 但由于生命、宇宙和我们在 A 之前得到 B 的一切。你知道这一点是因为你是人您可以可视化这些方案。

例如,涉及液体存储提供商的场景,该供应商利用基于平板电脑的应用程序记录其所有存储罐的每日审核,包括跟踪测量。试想一下,如果:

  1. Fred 在每日发货前测量存储,并记录了 47 升的每日测量。但是,由于存储设施缺乏适当的互联网连接,Fred 在此期间处于脱机状态。
  2. 爱丽丝坐在办公室里,将同一储罐的液位从前几天的读数从50升更新到150升,因为她刚刚接到现场技术员的通知,说他们每天的交货刚刚收到,送货司机在浇注油箱后通知了 Alice 正确的读数。
  3. 当天晚些时候,弗雷德终于连接到互联网,他以前录制的读数可以传输。

在这种情况下,尽管 Fred 的传输发生在 Alice 的更新之后,但我们的人类直觉是,弗雷德的更新应该被忽略。也许保留,在审计的情况下,或一个要求太高的老板想知道为什么弗雷德从来没有提交他的读数为这个坦克,但可以肯定的是,我们并不期望弗雷德的读数应该取代一个爱丽丝输入系统。

这是因为弗雷德的更新在 Alice 记录的更新之前按时间顺序发生,而其中就是我们的解决方案。我们的系统应保留并遵守更新应发生的时间顺序,而不一定是在中央服务器接收更新时。事实上,服务器接收更新的时间更像是一个技术工件,尽管是一个令人好奇的工件,但肯定不是系统普通用户感兴趣的一个。

为了纠正这种情况,我们需要向我们打算允许脱机更新的每个模型添加另一个字段。此字段应跟踪用户指示他们希望执行操作的精确时间戳。让我们称之为”提交日期”。

重温我们上面的例子,Fred 更新的提交日期可能是 11 月 1 日上午 9 点,而 Alice 的提交日期可能是同一天上午 11:45。如果我们捕获了这些时间戳,当 Fred 的记录到达时,我们可以清楚地看到,他正在提交一个值,该值是打算早到的。事实上,我们甚至可以看到,我们有一个较新的记录,并可以选择忽略他的更新。

此时,您可以看到此附加时间戳的重要性。我们的系统正在累积大量时间戳,我们更新了日期,并创建了日期来帮助促进向下同步,现在我们添加了一个提交日期来促进上同步。这仅仅是因为,在所有类型的同步中,最大的问题是时间顺序不一致的问题 – 计时错误。我们可以通过保持良好的簿记来防止这种情况,由于我们关注的是时间,这意味着我们只是保留大量时间戳作为簿记,以确保数据的正确流动。

删除内容如何?

我们已经设置了处理删除的基础知识。使用我们新添加的提交日期时间戳,我们现在可以很容易地忽略删除记录的更新…如果计时正确。你看,尽管添加了一个新的时间戳,我们已经掩盖了另一个计时问题。删除操作将它们具有前置和居中,因为删除实际上是逻辑删除(您不难删除记录,是吗?

事实上,在另一次更新之前/之后发生的删除冲突只是发生两次更新的特殊情况,但在同一模型的不同字段上。

换句话说,如果我们能解决爱丽丝更新坦克的每日流体读数,同时到离线更新从Fred试图更新相同的坦克的压力读数,那么我们也可以解决删除。让我们仔细看看这些场景,看看我们缺少什么,为什么。

  1. Fred 访问存储站点 4, 并记录存储罐 B 的每日压力读数 – 比比 60 Psi 。但是,Fred 处于脱机状态,因此他的更新尚未传输。
  2. Alice 接到一个电话,通知她 4 号工地收到 B 罐的额外液体。她在线,并立即更新液体读数到150升。
  3. 弗雷德终于得到了互联网的导电性, 并传递了他的压力读数从当天早些时候。

这很多都归结到我们如何构建数据,让我们让这一切更加具体。假设我们的架构如下所示:

到第 3 步到达时,我们的记录将看起来像这样:

如果我们的系统,注意到这是更早的时间戳,只是忽略了Fred的提交,那么我们只是抛出的数据,因为只有弗雷德的记录包含压力读数。怎么办?

救援的实体属性值

幸运的是,我们存在一个模式来解决这个确切的问题,它被称为实体属性值模式。EAV 模式实际上非常简单,一旦集成,可以大大简化同步的复杂性。

在其核心,EAV 说 (E) 实体(因此流体罐读数的表示)包含多个 (A) 属性(我们的罐的液位和压力读数)应用单独的 (V) 值表示时记录。简言之,我们应该修改我们的架构,一个记录说”液位是150升”,另一个记录说”压力是60 psi”,但两者都不说两者。困惑?让我们看看上面的架构在 EAV 下的外观, 然后从那里开始:

 

在此修订的架构中,我们使用”阅读”字段来达到双重目的。有时阅读是压力,有时是卷。两者如何区分?这就是阅读类型字段的用。在此示例中,类型 1 表示压力读数,类型 2 表示液位。这些类型是完全特定于应用程序的,所以不要挂上,而是观察如何在这个修订的架构中,我们基本上已经旋转我们的记录,并允许保留爱丽丝和弗雷德的更新没有任何冲突,所以永远什么。

事实上,如果我们在先前的Fred示例中使用此架构并循环,也可以记录液位,我们会得到一些非常有趣的东西:

 

在这里,我们不仅使用了新架构,还允许 Fred 的早期压力读数存储到数据库中。由于我们使用的是 EAV 模式,并且具有适当的 submission_date 时间戳,因此当我们查询正确/当前流体读数时,我们可以清楚地看到 Fred 的流体读数发生得更早,并且忽略了它。但是,我们仍记录它用于审核目的。此外,也许更重要的是,它更容易。每当我们选择性地更新数据时,我们都会面临出错的风险。但是,如果我们将所有内容都写进本质上是一个巨大的审核日志中,我们可以(在查询时)实时解决冲突 – 每个新保存的记录都追溯性地解决所有以前的冲突。

最后一点值得多讨论一下。

虽然我们到目前为止的例子非常简单,但时间更新可能会变得相当复杂。掷骰子足够,你可以有来自同一个人的多个更新,一些在线,一些离线,其他更新从不同的用户,等等,所有发生并行。您可以在每个新接收的记录上评估系统中的所有现有记录,并确定是否应该插入或更新该行,但如果在评估是否应记录 Alice 的更新时,来自其他用户(如 Bob)的另一个更新将到达,则会发生什么。如果您运行的是多个 Web 服务器(就像几乎任何现代 Web 应用程序一样),那么评估是否应保存 Alice 记录的逻辑是同时运行到 Bob 的,并且两者都对另一个服务器一无所知 – 更多的冲突!

相反,上述模式允许将 Alice 和 Bob 的更新写入数据库,当其他人读取数据时,仅使用具有最新时间戳的记录(由提交日期确定)。

 

About Jason

Jason 是一位经验丰富的企业家兼软件开发人员,擅长领导力、移动开发、数据同步和 SaaS 架构。他获得了阿肯色州立大学计算机科学学士学位( 学士学位.
View all posts by Jason →

发表评论

邮箱地址不会被公开。 必填项已用*标注