【问题标题】:System design: Strategies for dealing with heavy writes to a DB系统设计:处理对数据库的大量写入的策略
【发布时间】:2019-04-01 22:14:21
【问题描述】:

从系统设计/可扩展性的角度来看,在处理需要大量写入数据库中特定表的系统时,有哪些行业标准策略。

为简单起见,假设该表是产品的库存表,并具有“产品名称”列和“计数”列,每次购买新产品时它都会简单地增加 +1系统。每隔 2 次就有数百万用户购买不同的产品,我们必须跟踪每种产品的最新计数,但这不一定是严格实时的,也许 5 分钟的延迟是可以接受的。

我的选择是:

1) 主从复制,其中主数据库处理所有写入,从属处理读取。但这并不能解决写繁重的问题

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

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

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

感谢任何见解!

【问题讨论】:

  • 您是否受限于特定的数据库?众所周知,Cassandra 具有高写入吞吐量,并且按设计分布以实现可扩展性/高可用性
  • 我对允许高写入吞吐量的技术和原理更感兴趣,而不是在哪个框架或数据库上。

标签: architecture scalability system-design


【解决方案1】:

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

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

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

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

【讨论】:

  • 1.据说从多个进程并行更新相同的记录会使进程相互等待=>在中等负载下系统会崩溃,而不是“排队会导致整个系统崩溃”。 2. 无论您使用哪种排队系统,您都需要决定什么是最重要的 - 耐用性或速度,因为您仍然需要将项目保存在某个地方,并且将每个请求刷新到磁盘并不总是可行且合理的立即而不是在内存中累积一段时间,所以总是有机会丢失一些数据
  • 3.我并不是建议仅使用“内存缓存”-请再读一遍 4. 在作者建议的特定情况下,您使用中间队列并为每个增量请求创建一条新消息是否正确?跨度>
  • 感谢您澄清您的回答。是的,您正确理解了我的回答。要获得处理每个增量请求的最高可靠性,您需要一个队列。
  • 在您的回答中,您建议每个请求都更新内存缓存。这不可靠,并且比排队命令要慢。另外,我希望您注意到这个问题澄清了可用性与一致性。 5 分钟的延迟意味着最终的一致性是可以接受的。如果命令数量远高于查询数量,我不建议对每个请求执行任何操作。无论如何,这至少澄清了您的建议和我的建议之间的差异。
【解决方案2】:

我会说解决方案将高度依赖于您需要做什么。每秒写入数千个 record 的解决方案可能与您提供的示例中增加一个 counter 非常不同。更重要的是,根本没有tables 来处理这样的负载。 Consistency/availability 您的问题中也缺少要求,并且根据它们,整个架构可能会有很大不同。

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

选项1(主从复制)

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

选项 2(分片 DB)

您的假设是正确的,与第 1 页差别不大

选项 3(批量更新)

非常接近。由轻量级存储提供的缓存层,提供并发原子增量/减量和持久性,不会丢失您的数据。我们已经将redis 用于类似目的,尽管任何其他key-value database 也可以这样做——实际上周围有几十个这样的数据库。

键值数据库或键值存储是一种数据存储范例 设计用于存储、检索和管理关联数组, 数据结构在今天更普遍地称为字典或哈希表

解决方案如下:

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

您将运行一个“刷新”脚本,即*/5,它执行以下操作(简化):

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

进一步缩放

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

【讨论】:

  • 感谢您的详细解答!追问:这个kv_storage,比如Redis,需要单独的DB来持久化吗?所以我会有 server->redis->db 存储?如果是这样:传入请求→您的后端服务器-> kv_storage(原子增量(product_id))这实际上不会写入任何数据库,只是更新内存中的redis缓存,对吗?持久化是由刷新脚本每 5 分钟完成一次?
  • 已经有了。您可以配置它的可靠性 - 有多个选项,请参阅 redis.io/topics/persistence 通常在性能和可靠性之间进行权衡
  • 我可以将 redis 与不同的后端数据库存储一起使用,例如传统的 MySQL 吗?
  • 你不能,它有自己的基于文件的存储系统,它的所有操作都是围绕它构建的。它比mysql简单得多,所以不用担心
猜你喜欢
  • 2011-01-27
  • 2010-09-24
  • 1970-01-01
  • 2013-05-17
  • 2011-03-23
  • 2021-10-30
  • 1970-01-01
  • 2013-08-10
  • 1970-01-01
相关资源
最近更新 更多