系统设计:处理对数据库的大量写入的策略
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
执行以下操作(简化):
- 对于 kv_storage 中的每个
product_id
读取其当前 value
- 更新您的数据库计数器 (
+= value
)
- 减少 kv_storage 中的
value
进一步扩展
- 如果脚本失败,也不会发生什么坏事 - 更新会在下一个 运行
到达
- 如果您的后端盒子无法处理负载 - 您可以轻松添加更多盒子
- 如果单个键值数据库无法处理负载 - 它们中的大多数都支持扩展多个框,或者后端脚本中的简单分片策略可以正常工作
- 如果单个 "flushing" 脚本跟不上增量 - 您可以将它们扩展到多个框并决定每个框处理的键范围
您问了一个典型的 CQRS 问题。 "CQRS" 代表命令查询责任分离。这就是它听起来的样子——你把你的写(命令)和你的读(查询)分开了。当您在写入和读取之间有不同的需求时,这种方法可以解决问题 - 正是您的情况。
要以可扩展的方式实现此目的,您需要确认(即接受)递增请求,并将其排队进行处理。并让读取根据请求实时工作。使用知道如何协调 的后台 命令处理程序处理排队的请求。即,如果它失败了,它应该知道如何解决冲突(例如,如果其他人更新了该行,检索更新的版本并重试)。
我完全不同意另一个答案,其中有人建议排队会降低您的整个系统。排队不会带来任何影响,因为它是排队而不是实时处理。这就是缩放的重点。恰恰相反 - 实时进行更改,即使这意味着只是更改内存缓存中的布尔标志,也比排队更糟糕。试想一下,如果内存中的缓存在那一刻关闭会发生什么。异步离线(后台)处理可确保此类问题不会阻止命令最终得到处理。
但是,您可能需要缓慢地处理排队的命令(在不影响读取的情况下可以处理任何速度)或在单独的数据副本中处理。
您可以像其他人建议的那样使用内存缓存等特定技术,但这又是 CQRS 范例的另一种实现。它可以是缓存,也可以只是记录或数据库的另一个副本。同样的东西,同样的效果。
从系统 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
执行以下操作(简化):
- 对于 kv_storage 中的每个
product_id
读取其当前value
- 更新您的数据库计数器 (
+= value
) - 减少 kv_storage 中的
value
进一步扩展
- 如果脚本失败,也不会发生什么坏事 - 更新会在下一个 运行 到达
- 如果您的后端盒子无法处理负载 - 您可以轻松添加更多盒子
- 如果单个键值数据库无法处理负载 - 它们中的大多数都支持扩展多个框,或者后端脚本中的简单分片策略可以正常工作
- 如果单个 "flushing" 脚本跟不上增量 - 您可以将它们扩展到多个框并决定每个框处理的键范围
您问了一个典型的 CQRS 问题。 "CQRS" 代表命令查询责任分离。这就是它听起来的样子——你把你的写(命令)和你的读(查询)分开了。当您在写入和读取之间有不同的需求时,这种方法可以解决问题 - 正是您的情况。
要以可扩展的方式实现此目的,您需要确认(即接受)递增请求,并将其排队进行处理。并让读取根据请求实时工作。使用知道如何协调 的后台 命令处理程序处理排队的请求。即,如果它失败了,它应该知道如何解决冲突(例如,如果其他人更新了该行,检索更新的版本并重试)。
我完全不同意另一个答案,其中有人建议排队会降低您的整个系统。排队不会带来任何影响,因为它是排队而不是实时处理。这就是缩放的重点。恰恰相反 - 实时进行更改,即使这意味着只是更改内存缓存中的布尔标志,也比排队更糟糕。试想一下,如果内存中的缓存在那一刻关闭会发生什么。异步离线(后台)处理可确保此类问题不会阻止命令最终得到处理。 但是,您可能需要缓慢地处理排队的命令(在不影响读取的情况下可以处理任何速度)或在单独的数据副本中处理。
您可以像其他人建议的那样使用内存缓存等特定技术,但这又是 CQRS 范例的另一种实现。它可以是缓存,也可以只是记录或数据库的另一个副本。同样的东西,同样的效果。