【高并发写】库存系统设计

2023-11-29 10:38:30 浏览数 (2)

点击下方“JavaEdge”,选择“设为星标”

第一时间关注技术干货!

免责声明~ 任何文章不要过度深思! 万事万物都经不起审视,因为世上没有同样的成长环境,也没有同样的认知水平,更「没有适用于所有人的解决方案」; 不要急着评判文章列出的观点,只需代入其中,适度审视一番自己即可,能「跳脱出来从外人的角度看看现在的自己处在什么样的阶段」才不为俗人。 怎么想、怎么做,全在乎自己「不断实践中寻找适合自己的大道」

当 DoorDash 从订购餐饮向便利店和杂货(CnG)业务转型时,他们必须找到一种方法来管理每个商户每个店铺的在线库存,从几十种商品增加到数万种商品。 为了解决这个扩展问题,他们的团队构建了一个高写入量的库存平台,它将能够跟上平台上的所有更改。

0 大纲

  1. 支持 CnG 库存管理的挑战
  2. 他们理想库存平台的技术需求
  3. 功能架构
  4. MVP 后对解决方案的增量更改 —— 将单个商品 API 更改为批量 API —— 数据库表优化 —— 在一个请求中批量上传 CockroachDB 的数据库
  5. 结论

1 支持 CnG 库存系统的挑战

DoorDash 每天以三种不同方式多次刷新 CnG 商户的库存:

  • 通过摄入从商户接收的平面库存文件自动更新
  • 他们的运营团队通过内部工具加载库存数据
  • 通过在 CnG 商店购物的 Dash 运送应用中的信号更新库存

由于 CnG 商店的数量数万家,且每家商店可能包含数万种商品,刷新可能每天涉及超过 10 亿件商品。

2 期望的库存平台的技术需求

2.1 高可扩展性

随着他们的业务增长,库存平台需要支持更多添加到系统中的商品。需要支持频繁的更新,以保持库存的新鲜度

2.2 高可靠性

流水线应该可靠,以便所有来自商户的有效库存更新请求最终都能成功处理

2.3 低延迟

商品数据非常敏感,特别是价格和可用性属性。从获取商业数据到向客户显示数据之间的时间间隔应尽可能小。

2.4 高可观察性

流水线应具有大量验证和防护栏。

3 功能架构

从他们的库存摄入管道的高级体系结构开始。

下图显示他们库存摄入流水线的顶层设计,一个异步系统,从多个不同来源摄入库存,对其进行处理并传递给下游系统,在那里为面向客户的实体提供视图。

  1. API controller,基于 gRPC 的 API 控制器,充当平台上库存数据的入口点。
  2. Raw Feed Persistence —— API 控制器之后的大部分库存处理都是异步的,并通过 Cadence 工作流执行的。
  3. Hydration—— 商店商品的详细视图涉及库存和目录属性。DoorDash 的库存摄入管道负责给(即富集)原始库存信息添加目录属性。
  4. 价格计算 —— 他们还依赖从依赖服务获取的外部配置按需执行每件商品的价格计算。
  5. 无库存预测分类 —— 预测模型,通过学习历史订单和 INF(商品未找到)数据,对商品是否可以在店内提供进行分类。
  6. Guardrails—— 没有管道不会由于代码错误在他们自己的系统和/或上游系统中的问题而导致错误。当满足某些条件时,库存平台需要建立尽力而为的防护栏(和报警机制)来检测和限制更新。
  7. 可观察性 —— 在商品层面及商店层面(聚合统计数据)都能完全看到此管道非常重要。我们需要知道是否由于管道中的某些错误而丢弃了某个商品,因为这直接与商品在商店页面上不可用有关。
  8. 可靠性 —— 由于大量的计算和依赖服务,他们的库存管道需要是异步的。Cadence 是一种无故障和有状态的工作流编排器,满足了他们的这一职责。

4  MVP 后的解决方案的增量更改

4.1 将单个商品 API 更改为批量 API
  • MVP 版本,构建了一个单个商品的 API,要创建/更新一个商品,调用者需要调用他们的 API 一次。
  • 如果一个商店有 N 个商品,调用者将需要调用 N 次 API,这可并行发生
  • 让我们再次考虑用例:当他们更新一个商店时,调用者已经知道完整的商品列表,他们可以通过一次 API 调用发送完整的商品列表。最常见的用例将使其可以批量商品并在一次请求中将它们发送到他们的服务。他们的服务可以将有效负载保存到 S3 并通过 Cadence 作业异步消耗它。

改为批量 API 后,观察到处理速度有所改进,但仍需达期望水平。

4.2 数据库表优化

随着他们在每个步骤上添加更多指标,他们发现数据库访问是一个重要的瓶颈:

  • 选择自然主键而不是自动递增主键 —自然复合键帮助他们更有效减少列和查询
  • 清理数据库索引 —为所有查询添加缺失的索引并删除不必要索引
  • 减少列数 —表最初有约 40 列,大多数情况,所有列都可同时更新。因此,他们决定将一些频繁更新列放入一个 JSONB 列
  • 为快速增长的表配置TTL — 为保持数据库容量和后续查询负载在可控范围,确定了一些高强度写入的表,这些表不需要保存太长时间数据,并在 CockroachDB 中为这些表添加TTL配置
  • 数据库和依赖检索逻辑从商品级别修改为商店级别 —要更新一个商品,需从商店级别和商品级别获取大量信息,如商店级通货膨胀率和商品级目录数据。选择按需获取该信息,因为正在处理每个商品。通过这样做,他们可以为下游服务和数据库节省大量 QPS,并为他们的系统以及他们的系统改善性能
4.3 在一个请求中将数据库插入批量化到CockroachDB

每次完成商品级处理后,都通过使用单商品插入将结果保存到数据库中——这在数据库中造成非常高 QPS。

与存储团队讨论后,建议批量 SQL 请求。因此调整体系结构:

  • 在完成每个商品处理后,收集结果并将其保存在进程的内存
  • 然后将查询聚合为每批 1,000 个,并在一个 SQL 请求中发送批处理

修改查询重写后,观察到应用层和存储层的服务性能显著提高:

  • 每件商品的处理时间减少了 75%
  • 存储 QPS 下降 99%
  • 存储 CPU 利用率下降

5 总结

  • 构建和扩展数字库存很难,因为数字库存的数据大小可能巨大,同时它需要准确提供正确的实时库存视图
  • 而且它对时间也很敏感,因为一获得商品信息我们就需要向客户显示商品的正确价格和可用性

主要:

  • 在实现的开始,努力创建一个详尽的指标监控面板,以便在出现性能问题时,可轻松缩小系统的瓶颈。通常,从一开始就可以对实时系统具有高可见性非常有用
  • 可帮助读写模式的数据方式保存数据。库存数据可能不是扁平的数据列表 —— 它们可能具有一定级别层次结构。它们可保存为商品级别或商店级,这完全取决于确定服务的读写模式
  • 尽可能设计批量 API 和 DB。大多情况下,更新库存时,我们会更新一整个商店或地理位置的库存。无论哪种,都有多个要更新的商品,所以最好尝试批量更新而非每个请求或查询更新单个商品
  • 若业务部门允许异步处理,使计算异步化,并为每个单元(商店或商品)建立强大SLA。单个商品处理时间包括花费在网络通信上的时间,当有数十亿商品要处理时这些时间会积累。
  • 相反,若我们通过一个请求发送整个商店的库存,并在服务器端使用 blob 存储保存请求有效负载并异步处理,则客户端可节省等待时间,服务能具有高吞吐量
  • 从这角度看,还建立了内容将在近实时而非实时更新的想法。Cadence 是处理近实时作业的好工具,并具有许多内置功能来改进系统可靠性和效率。

参考:

  • https://doordash.engineering/2023/02/22/how-doordash-designed-a-successful-write-heavy-scalable-and-reliable-inventory-platform/?source=post_page-----61c1a5c71641--------------------------------

0 人点赞