库存系统架构设计:单体 vs 微服务,接口与分库分表

聊到库存系统的架构,我见过太多团队一上来就搞微服务。结果呢?分布式事务还没搞定,先把自己绕晕了。今天咱们就掰扯清楚,单体架构和微服务架构到底怎么选,核心接口怎么设计,以及数据库分库分表那点事。

一、单体架构 vs 微服务架构:别盲目跟风

先说说单体架构。说白了,就是把所有功能都塞到一个应用里。库存、订单、商品、用户……全在一个进程里跑。好处很明显:开发简单,部署方便,事务好控制。我早期做的一个电商项目,日订单量不到10万,单体架构跑得稳稳的。

但问题也来了。一旦业务膨胀,比如双十一流量暴增,单体架构就扛不住了。你想单独扩容库存服务?不行,得整个应用一起扩。而且代码耦合严重,改个库存逻辑可能影响订单模块。嗯,这里要注意:单体架构适合业务初期、团队规模小、并发量不高的场景

微服务架构呢?把库存拆成独立服务,跟订单、商品、支付等服务解耦。每个服务可以独立部署、独立扩容、独立维护。我在项目中遇到过,库存服务需要高频读写,而订单服务主要是写操作,两者对数据库的压力完全不同。拆开后,库存服务用了Redis集群,订单服务还是MySQL,各自优化,互不影响。

但微服务也有坑。最头疼的就是分布式事务。库存扣减和订单创建,原本在一个数据库里用本地事务就能搞定,拆成两个服务后,就得引入TCC、Saga或者可靠消息最终一致性方案。我曾经因为分布式事务没处理好,导致库存超卖,赔了不少钱……

核心结论:

  • 业务初期、团队<10人、日均订单<10万 → 单体架构
  • 业务复杂、团队>20人、日均订单>50万 → 微服务架构
  • 中间状态 → 先单体,预留拆分接口,后期逐步演进

二、库存服务的核心接口设计

不管用单体还是微服务,库存服务的接口设计都绕不开这几个核心操作。我个人习惯把接口分为三类:查询类、操作类、管理类。

2.1 查询类接口

说白了就是查库存。但别小看这个接口,并发场景下很容易出问题。我建议提供两个粒度:

  • 实时库存查询:直接从Redis查,毫秒级返回
  • 精确库存查询:从MySQL查,用于对账和人工复核
// 实时库存查询(Redis)
GET /api/v1/inventory/{skuId}/realtime
Response: { "skuId": "10001", "available": 500, "locked": 20 }

// 精确库存查询(MySQL)
GET /api/v1/inventory/{skuId}/exact
Response: { "skuId": "10001", "total": 1000, "sold": 480, "locked": 20, "available": 500 }

2.2 操作类接口

这是核心中的核心。库存扣减、库存回滚、库存预占,这三个接口必须保证原子性。你想想看,用户下单时先预占库存,支付成功后再真正扣减,支付失败或超时则回滚。这个流程一旦出问题,要么超卖,要么少卖。

// 预占库存(下单时调用)
POST /api/v1/inventory/preoccupy
Request: { "skuId": "10001", "quantity": 2, "orderId": "O20231001" }
Response: { "success": true, "lockedId": "L20231001" }

// 确认扣减(支付成功时调用)
POST /api/v1/inventory/confirm
Request: { "lockedId": "L20231001" }
Response: { "success": true }

// 回滚库存(支付失败或超时)
POST /api/v1/inventory/rollback
Request: { "lockedId": "L20231001" }
Response: { "success": true }

避坑指南:我曾经在预占库存时只更新了Redis,没同步写MySQL。结果Redis宕机,库存数据全丢了,导致大量超卖。后来我强制要求:预占操作必须同时写Redis和MySQL,且以MySQL为准

2.3 管理类接口

这些接口给运营和后台用,比如批量导入库存、调整安全库存阈值、查看库存流水等。没什么技术难点,但要注意权限控制和操作审计。

三、数据库分库分表策略

库存系统的数据量增长很快。一个中型电商,SKU数量可能上百万,每天库存流水上千万条。单库单表肯定扛不住。分库分表是必然选择。

3.1 分库策略

我建议按业务域分库。库存相关的表放在一个库里,订单相关的放另一个库。这样每个库的数据量可控,也方便做读写分离。举个例子:

数据库 包含表 说明
inventory_db inventory, inventory_locked, inventory_flow 库存核心数据
order_db order, order_item, order_payment 订单相关数据
product_db product, sku, category 商品基础数据

3.2 分表策略

分表的核心是选对分片键。库存系统最常用的分片键是SKU ID。为什么?因为库存查询和操作都是基于SKU的。按SKU ID哈希分表,同一个SKU的数据都在同一张表里,避免跨表查询。

我习惯用一致性哈希算法。这样当需要扩容分表时,只需要迁移少量数据。我曾经用取模分表,结果从16张表扩到32张表时,数据迁移搞得焦头烂额……

-- 分表规则:按SKU ID哈希,共64张表
-- 表名:inventory_00, inventory_01, ..., inventory_63

-- 查询时计算分表
SELECT * FROM inventory_{hash(sku_id) % 64} WHERE sku_id = '10001';

注意:分表后,跨SKU的统计查询会变得很麻烦。比如查所有SKU的总库存,需要遍历所有分表。我的做法是:用Elasticsearch或ClickHouse做离线统计,MySQL只负责实时读写

四、架构图:库存系统核心逻辑

下面这张图展示了库存系统的核心架构。从客户端请求到数据库存储,每一步都涉及关键设计。我画出来供你参考。

库存系统核心架构图 客户端 API网关 库存服务 Redis缓存 MySQL分库分表 inventory_00 ~ inventory_63 离线统计 ES/ClickHouse 请求流程:客户端 → API网关 → 库存服务 → Redis缓存 → MySQL分库分表 离线统计:ES/ClickHouse用于报表和数据分析

这张图里,我特意把Redis放在MySQL前面。为什么?因为库存查询和扣减都是高频操作,直接查MySQL扛不住。Redis做缓存,命中率能达到95%以上。但注意,Redis不能作为唯一数据源,MySQL才是最终数据归宿。

五、总结一下

架构设计没有银弹。单体架构简单可靠,微服务架构灵活可扩展,关键看你的业务阶段。接口设计要围绕「预占-确认-回滚」这个核心流程,保证原子性。分库分表选对分片键,用一致性哈希避免数据迁移的坑。

嗯,这些经验都是我踩过坑之后总结出来的。希望你能少走弯路。

一句话记住:库存系统的核心是「数据一致性」,架构设计、接口设计、数据库设计,都是为这个目标服务的。

专注资料整理