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

时间:2018-10-29 01:48:18

标签: architecture scalability system-design

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

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

我的选择是:

1)主从复制,其中主数据库处理所有写操作,而从数据库处理读操作。但这并不能解决大量写入的问题

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

3)批量更新?使用某种缓存并每X秒钟写入表一次,累积计数是否等于这X秒钟我们收到的所有计数?那是一个有效的选项,我使用什么缓存机制?如果最后一次读取和下一次写入之间发生崩溃怎么办?如何恢复丢失的计数?

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

任何见识都受到赞赏!

2 个答案:

答案 0 :(得分:4)

我要说的是,解决方案将高度取决于您真正需要做的事情。每秒写入数千条记录的解决方案可能与您提供的示例中增加 counter 的方式大不相同。更重要的是,根本没有tables可以处理这种负载。您的问题中也缺少Consistency / availability的要求,并且根据它们的要求,整个体系结构可能会非常不同。

无论如何,回到您的简单案例和选项

选项1(主从复制)

您将在这里遇到的问题是数据库locking-每个增量都将需要一个记录锁,以避免出现竞争情况,并且您将迅速使写入队列的进程在队列中等待并且系统崩溃。即使在中等负载下)

选项2(共享数据库)

您的假设是正确的,与第1页相差无几

选项3(批量更新)

非常接近。轻量级存储提供的缓存层提供并发的 atomic 增量/减量和持久性,不会丢失数据。我们已经将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

进一步缩放

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

答案 1 :(得分:0)

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

要以可扩展的方式实现此目的,您需要确认(即接受)一个增加请求,并将其排队等待处理。并让每个请求的读取实时进行。使用背景命令处理程序处理排队的请求,该命令处理程序知道如何协调。也就是说,如果失败,它应该知道如何解决冲突(例如,如果其他人更新了该行,则检索了较新的版本,然后重试)。

我完全不同意另一个答案,有人建议排队会降低整个系统的性能。排队不会降低任何性能,因为它是排队而不是实时处理。这就是扩展的重点。相反,实时更改(即使这意味着仅更改内存缓存中的布尔标志)比排队要糟糕得多。试想一下,如果在那一刻内存不足的缓存关闭,将会发生什么。异步脱机(后台)处理可确保此类问题不会阻止命令最终被处理。 但是,您可能需要缓慢处理排队的命令(无论它能处理多少步速而不会影响读取),或者在单独的数据副本中处理。

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