1. 项目概述
这个库存管理系统是我去年为一家中型电商企业开发的内部管理工具,核心目标是解决他们长期存在的库存数据不准确、人工盘点效率低下问题。系统采用Spring Boot框架开发,前后端分离架构,对接了企业现有的ERP系统,实现了从商品入库到出库的全流程数字化管理。
提示:在电商行业,库存准确率每提升1%就能减少约3%的运营成本,这是很多企业愿意投入资源做库存系统的根本原因。
系统上线后运行稳定,日均处理2万+库存变动记录,将原本需要4小时的日盘点工作缩短到15分钟完成。下面我会从技术选型、核心模块、实现细节和踩坑经验几个维度,完整还原这个项目的开发过程。
2. 技术架构设计
2.1 技术栈选型
选择Spring Boot作为基础框架主要基于以下考量:
- 快速启动:内嵌Tomcat简化部署,符合企业"两周出原型"的要求
- 生态完整:Spring Data JPA + QueryDSL完美解决复杂库存查询需求
- 可扩展性:后期需要对接WMS系统时,Spring Integration能提供现成方案
核心组件版本:
<spring-boot.version>2.7.5</spring-boot.version> <hibernate.version>5.6.12.Final</hibernate.version> <querydsl.version>5.0.0</querydsl.version>2.2 系统架构图
[客户端] ←HTTP→ [Spring Boot] ←JDBC→ [MySQL] ↑ ↓ [Redis] [RabbitMQ]关键设计决策:
- 采用Redis缓存热点商品库存(如促销品),设置5分钟本地缓存+Redis二级缓存
- 库存变动通过RabbitMQ异步处理,避免高并发下的数据库锁竞争
- 使用MySQL窗口函数实现库存变化趋势分析
3. 核心功能实现
3.1 库存扣减设计
最关键的库存扣减接口需要考虑并发安全问题,我们最终采用方案:
@Transactional public boolean deductInventory(Long skuId, int quantity) { // 使用SELECT...FOR UPDATE实现行级锁 Inventory inventory = inventoryRepository.findBySkuIdForUpdate(skuId); if(inventory.getAvailableQuantity() < quantity) { return false; } inventory.setAvailableQuantity(inventory.getAvailableQuantity() - quantity); // 记录库存变更流水 inventoryLogRepository.save(new InventoryLog(skuId, quantity, "DEDUCT")); return true; }注意:实际生产环境还需要处理分布式锁问题,我们通过Redisson实现了跨JVM的锁管理
3.2 库存预警模块
预警规则配置采用策略模式实现:
public interface AlertStrategy { boolean check(Inventory inventory); } // 示例:低于安全库存预警 @Component public class SafetyStockStrategy implements AlertStrategy { @Override public boolean check(Inventory inventory) { return inventory.getAvailableQuantity() < inventory.getSafetyStock(); } }预警触发后系统会:
- 在企业微信发送通知
- 在管理后台生成采购建议单
- 记录预警历史供分析使用
4. 性能优化实践
4.1 热点库存优化
针对"秒杀商品"这类热点数据,我们设计了三级缓存方案:
- 本地缓存(Caffeine):有效期30秒,最大1000个SKU
- Redis集群:库存预扣减,采用Lua脚本保证原子性
- 数据库:最终一致性,通过定时任务同步
关键Redis Lua脚本:
local key = KEYS[1] local change = tonumber(ARGV[1]) local current = tonumber(redis.call('GET', key) or '0') if current + change >= 0 then redis.call('INCRBY', key, change) return 1 else return 0 end4.2 批量处理优化
对于入库单这类批量操作,我们改用了JDBC批量插入:
@Transactional public void batchImport(List<Inventory> items) { jdbcTemplate.batchUpdate( "INSERT INTO inventory(...) VALUES(...)", new BatchPreparedStatementSetter() { // 实现批量参数设置 }); }实测数据:处理1000条入库记录从原来的12秒降低到1.3秒
5. 踩坑经验总结
5.1 事务失效问题
遇到过@Transactional注解不生效的情况,最终发现两个关键点:
- 自调用问题:同类方法内调用不会触发AOP代理
- 异常捕获:catch住异常但没有重新抛出会导致事务不回滚
解决方案:
// 错误示例 public void updateStock() { try { deductInventory(); // 同类调用 } catch (Exception e) { log.error(e); // 吞掉异常 } } // 正确做法 @Transactional public void updateStock() { inventoryService.deductInventory(); // 通过Service调用 // 或者抛出RuntimeException }5.2 库存超卖排查
曾出现凌晨促销时库存超卖问题,最终定位原因是:
- 缓存与数据库不一致
- 没有做库存预占机制
改进方案:
- 引入库存预占表(hold_stock)
- 增加库存校验定时任务
- 实现自动补偿机制
6. 监控与运维
6.1 监控指标配置
通过Micrometer暴露的关键指标:
- inventory.change.count:库存变动次数
- inventory.lock.time:锁等待时间
- inventory.cache.hit:缓存命中率
Grafana监控看板包含:
- 实时库存水位
- 库存变动频率
- 预警触发统计
6.2 数据迁移方案
从旧系统迁移数据时,我们采用分批次迁移:
- 先迁移基础商品数据
- 再迁移当前库存快照
- 最后迁移6个月内的流水记录
迁移脚本示例:
INSERT INTO new_inventory(sku_id, quantity) SELECT product_code, stock_count FROM legacy_stock WHERE warehouse = 'EAST' LIMIT 5000 OFFSET 0;7. 扩展与演进
系统后续迭代增加了两个重要功能:
7.1 库存预测
基于历史销售数据的预测模型:
# 使用Prophet进行销量预测 model = Prophet(seasonality_mode='multiplicative') model.fit(df) future = model.make_future_dataframe(periods=30) forecast = model.predict(future)7.2 移动端支持
开发了PDA客户端功能:
- 扫码快速入库
- 库存盘点实时同步
- 库位导航功能
实现技术:
- 使用WebSocket保持长连接
- 采用QuaggaJS实现浏览器端扫码
- 离线模式使用IndexedDB暂存数据
这个项目让我深刻体会到,一个好的库存系统不仅要考虑技术实现,更要理解业务场景。比如我们最初设计的预警阈值是固定值,后来改为根据销售旺季动态调整,这个改进使无效预警减少了70%。在实际开发中,一定要多和仓库管理员沟通,了解他们的真实工作流程