多节点架构并发导致的数据库设计问题

Database design issues due to concurrency in multi-node architecture

我会尽力解决这个问题。如果需要任何说明,请告诉我。

环境:
应用程序部署在 AWS 上,多个实例连接到一个数据存储。
数据存储包括tables,

旧版 tables:

instance_info (id, instance_details, ...)
task_info (id, task_id, ...)

新增table:

new_table (id, instance_info_id, task_info_id, ...)  

架构设计:

  1. id - 在所有 table 中都是 PK。
  2. 在new_table栏中,
    • task_info_id 是 table task_info 和
    • 的外键
    • instance_info_id 就是 table instance_info.
    • instance_info_id & task_info_id.
    • 上存在唯一约束

问题:
当代码执行时,它将其操作划分(分叉)到多个独立和并行执行的线程中。完成后,这些线程加入并尝试将数据插入遗留 table 之一 - “task_info”。
现在,可能会出现这样的情况,这些多个线程(运行 在单个节点上并发)将成功地将多个条目填充到 table.

要求:
如果有多个线程并行工作,那么只有一个线程将记录插入 table “task_info”,而其他线程只更新它。

限制:

  1. 无法向 task_info table 添加唯一约束,因为这种方法破坏了重试机制的现有(遗留代码)功能。
  2. 在写入操作期间无法锁定整个 table,因为这最终可能会给我们带来性能问题。
  3. 一种使用“直写”机制(分布式内存缓存)的深思熟虑的方法,但是,如果我们将停机时间考虑在内,似乎存在疑问,这可能会导致数据丢失。

是否有任何有效的设计方法(在遗留 code/design 中有 minimal/no 变化)可以研究?

更新

实施解决方案有一些真正严格的限制(由于添加额外资源的成本)如下,

  1. 支持的数据库有 Oracle、SQL Server、MySQL & MariaDB。因此,锁定机制必须是可互操作的。
  2. 可以使用的资源有限制 - 数据库和 Memcache。
  3. 系统既可以部署在云端也可以部署在本地。
  4. 无法从应用程序中分离出模块,或者 create/depend 在新的外部服务上。我真的很喜欢 Rob 提出的想法,因为它们很优雅并且让框架为我处理了大部分的复杂性。但是,这会增加添加和维护资源的成本。

我猜架构和更改它的限制,使得找到正确且具有成本效益的解决方案变得复杂。

您正在寻找分布式锁管理器。对此有很多选择,但由于您已经在使用 AWS,因此您应该考虑 the one they built using DynamoDB as a lock-store。虽然三个是很多替代方案,如果您不喜欢 AWS 构建的那个,可以使用诸如 ZooKeeper 之类的东西来帮助维护分布式锁系统。

听起来 Rob Conklin 对这方面的了解比我多,所以一定要看看他的回答。

想到的一个选项是使用队列。我自己从未在应用程序中使用过这种方法,但理论上你的各种实例可以在队列中抛出它们喜欢的任何东西,它通过确保根据你想要的任何规则(如 FIFO -先进先出)。这意味着您永远不会有两次尝试锁定数据库的调用,因为队列会确保这从未发生过。

一些排队解决方案的另一个优点是它们可以存储 events/messages 并在以后回放或回放。这意味着您可以使数据库脱机并让事件收集在缓存中,然后在数据库备份后播放它们。

显然,您只需要一些逻辑来管理先创建/后更新方法。这将更容易,因为消息序列现在通过队列变得一致且更可预测。

2021 年 7 月 2 日更新

关于您的评论...就同步性而言,假设我正确理解您的问题 - 对队列的调用(或您前面的任何外观)通常是同步的,因此调用者不会必须四处等待,因为所发生的只是他们的呼叫被接受到队列中——这应该相对较快。一个潜在的问题是,如果调用软件假设它对数据库的调用是完整的,而实际上它可能仍在队列中——你是这个意思吗?如果是这样,很难根据目前所说的内容来判断正确的方法是什么。

What if multiple nodes "re-tried" (retry is a legacy functionality) the same process and all of those nodes started updating the database?

Façade 或 Proxy 模式在这里可能很有用,因为您有一个代理来管理对数据库的所有调用。这可能可能也有助于解决同步问题。

这里我有一个“Uber 代理”,它包含队列以帮助处理由多个 callers/instances 生成的随机性,以及一个执行实际数据库调用的实际代理组件。

关于代理的事情是你可以在其中编写逻辑来帮助它决定执行哪些调用以及忽略哪些调用等等。