系统设计:处理对数据库的大量写入的策略

System design: Strategies for dealing with heavy writes to a DB

从系统 design/scalability 的角度来看,在处理需要大量写入数据库中特定 table 的系统时,有哪些行业标准策略。

为简单起见,假设 table 是产品库存 table,并且有一个列 'Product Name' 和一个列 'Count',它只是每次将新产品购买到系统时增加 +1。每 2 天就有数百万用户购买不同的产品,我们必须跟踪每个产品的最新计数,但它不一定是严格实时的,也许 5 分钟的延迟是可以接受的 table。

我的选择是:

1)主从复制,其中master DB处理所有写,slaves处理读。但这并没有解决写入繁重的问题

2) 根据产品名称范围或其哈希值对数据库进行分片。但是如果有一个特定的产品(例如 Apple)在短时间内收到大量更新,它仍然会命中相同的数据库。

3) 批量更新?使用某种缓存并每隔 X 秒写入 table,并累积我们在这 X 秒内收到的任何内容的计数?这是一个有效的选项吗?我使用什么缓存机制?如果上一次读取和下一次写入之间发生崩溃怎么办?如何恢复丢失的计数?

4) 我忘记了其他明显的选择吗?

如有任何见解,我们将不胜感激!

我想说解决方案将高度依赖于您究竟需要做什么。每秒写入数千条 记录 的解决方案可能与在您提供的示例中递增 计数器 有很大不同。更重要的是,根本没有 tables 来处理这样的负载。 Consistency/availability 您的问题中也缺少要求,根据这些要求,整个架构可能会有很大不同。

无论如何,回到您具体的简单案例和您的选择

选项 1(主从复制)

你将面临的问题是数据库 locking - 每个增量都需要一个记录锁来避免竞争条件,你会很快让你的进程写入你的数据库在队列中等待和你的系统吃下。即使在中等负载下)

选项 2(分片数据库)

你的假设是正确的,与p.1没有太大区别

选项 3(批量更新)

非常接近。由轻量级存储提供的缓存层,提供并发 atomic incremens/decrements 和 persistence 不会丢失您的数据。我们将 redis 用于类似的目的,尽管任何其他 key-value database 也可以 - 确实有几十个这样的数据库。

A key-value database, or key-value store, is a data storage paradigm designed for storing, retrieving, and managing associative arrays, a data structure more commonly known today as a dictionary or hash table

解决方案如下所示:

incoming requests → your backend server -> kv_storage (atomic increment(product_id))

你将有一个 "flushing" 脚本 运行ning 即 */5 执行以下操作(简化):

  1. 对于 kv_storage 中的每个 product_id 读取其当前 value
  2. 更新您的数据库计数器 (+= value)
  3. 减少 kv_storage 中的 value

进一步扩展

  • 如果脚本失败,也不会发生什么坏事 - 更新会在下一个 运行
  • 到达
  • 如果您的后端盒子无法处理负载 - 您可以轻松添加更多盒子
  • 如果单个键值数据库无法处理负载 - 它们中的大多数都支持扩展多个框,或者后端脚本中的简单分片策略可以正常工作
  • 如果单个 "flushing" 脚本跟不上增量 - 您可以将它们扩展到多个框并决定每个框处理的键范围

您问了一个典型的 CQRS 问题。 "CQRS" 代表命令查询责任分离。这就是它听起来的样子——你把你的写(命令)和你的读(查询)分开了。当您在写入和读取之间有不同的需求时,这种方法可以解决问题 - 正是您的情况。

要以可扩展的方式实现此目的,您需要确认(即接受)递增请求,并将其排队进行处理。并让读取根据请求实时工作。使用知道如何协调后台 命令处理程序处理排队的请求。即,如果它失败了,它应该知道如何解决冲突(例如,如果其他人更新了该行,检索更新的版本并重试)。

我完全不同意另一个答案,其中有人建议排队会降低您的整个系统。排队不会带来任何影响,因为它是排队而不是实时处理。这就是缩放的重点。恰恰相反 - 实时进行更改,即使这意味着只是更改内存缓存中的布尔标志,也比排队更糟糕。试想一下,如果内存中的缓存在那一刻关闭会发生什么。异步离线(后台)处理可确保此类问题不会阻止命令最终得到处理。 但是,您可能需要缓慢地处理排队的命令(在不影响读取的情况下可以处理任何速度)或在单独的数据副本中处理。

您可以像其他人建议的那样使用内存缓存等特定技术,但这又是 CQRS 范例的另一种实现。它可以是缓存,也可以只是记录或数据库的另一个副本。同样的东西,同样的效果。