十万字长文深度解析 WES 架构设计与重构实践
朋友们好,感谢阅读,本文是一篇超长的文章,数了数,目前零零碎碎写了十余万字,还在慢慢整理中,我将系统性聊聊 WES 架构设计与重构实践,后续如果有精力,会继续增补一些内容。
注:
- 本文所有技术图片图床都在GitHub,需要网络代理才能流畅访问。
- 文章很长,因为是静态博客,所以加载速度会稍微慢一些。
- 能力和精力有限,文章内容仅代表个人观点。
- 有些图示画得比较粗糙~,我觉得清晰化的内容表达比图示的精美要更重要,实在是精力有限,简单图我都是让 chatGPT 帮我生成的。
- 感谢阅读,业务存在边界,但技术没有终点,拥抱变化!
目前计划写的内容主要涉及到一下几点:
- WES 在供应链、履约链路的系统定位
- 核心功能
- 应用架构
- 核心链路
- 三级库存模型
- 出库效率优化:热度移位、flowpick、超 A 不返库……
- 调度模型
- 数据一致性保障
- 策略规则治理
- 流程引擎
- .……
总览图:

一、WES 在供应链、履约链路的系统定位
在仓储领域的 WES(Warehouse Execution System,仓储执行系统)中,“上游系统”并不是唯一的一个,而是一组业务系统。从业务流 / 数据流角度来看,WES 主要承接“计划与业务决策”,向下驱动“设备与执行”。
1.1、最典型的上游系统
WMS(Warehouse Management System)——最核心上游
WMS → WES 传什么?
- 出库单 / 入库单/盘点单……
- 商品、库存调整单……
- 作业策略(拣选规则、优先级)
WES 的角色
- 将 WMS 的“业务任务”拆解为可执行任务
- 调度、控速、并发控制
- 动态调整执行顺序
OMS(Order Management System)
在电商 / 履约中心中非常常见。
OMS → WMS / WES
- 客户订单
- 交付 SLA
- 订单优先级(如加急 / 普通)
有两种架构:
OMS → WMS → WES
OMS → WES(直连,较少但存在)
ERP(企业资源计划系统)
更多是 宏观计划级别:
-
采购计划
-
发运计划
-
财务 / 主数据
通常 不直接驱动 WES,而是:
ERP → OMS / WMS → WES
TMS(运输管理系统)
在出库履约场景:
-
发车时间
-
运输批次
-
装车顺序
影响 WES 的:
-
出库任务优先级
-
波次切分规则
1.2、从“职责边界”看上下游
业务分层视角
【业务决策层】
ERP / OMS / TMS
↓
【仓储管理层】
WMS
↓
【执行编排层】
WES
↓
【车辆调度集群层】
WCS(WCS、RCS集群)
↓
【物理设备层】
输送线 / AGV / 堆垛机 / 机械臂
1.3、以电商履约链路为例
以电商履约链路为例,WES 在 WMS 的下游,不分场景下,OMS 和 ERP 也会直接对接 WES。
WES 的业务链路位置大致参考下图(做了一些简化):

二、WES 核心功能
狭义的 WES 系统更侧重调度执行层,它不是“管业务”,也不是“控设备”,而是“把业务计划高效、稳定、实时地执行下去”, 从这个层面上来说,WES 的核心功能是:任务编排、执行调度、节拍控制、状态协同与异常恢复:
- 将 WMS 的业务任务拆解并编排为可执行任务;
- 对任务进行调度、并发与节拍控制;
- 统一管理任务与设备状态;
- 在异常情况下保证任务可恢复执行;
- 作为业务系统与设备系统之间的执行中枢。
但实际在实际的系统中,WES 还会兼管 WMS 的许多功能,比如单据管理、仓内基础数据管理、库存管理等等。
2.1、WES 调度能力的核心功能
1 任务拆解与编排(Task Orchestration)
从“业务任务”到“可执行任务”
来源
- WMS 下发:入库单、出库单、波次、移库任务
WES 要做什么
-
拆单(一个出库单 → 多个拣选 / 搬运 / 输送任务)
-
建 DAG / 状态机(先拣 → 再合流 → 再出库)
-
任务依赖、前后置约束
2 执行调度与并发控制(Scheduling)
这是 WES 的“灵魂”
WES 不只是“发任务”,而是控制“怎么发、发多少、先发谁”。
典型能力
-
多队列调度(按库区 / 设备 / 业务类型)
-
优先级调度(加急单 / SLA)
-
并发度控制(每秒 N 个任务)
-
公平 / 抢占 / 权重调度
3 节拍控制 / 控速下发(Throttling)
防止设备被“打爆”
为什么要 WES?
- 下游车辆调度系统和搬运系统存在能力限制
WES 能力
-
QPS 控制(如:每秒最多 20 个任务)
-
滑动窗口 / 漏桶 / 令牌桶
-
按设备维度限流
-
动态调整速率(高峰 / 低谷)
从这个角度讲, WES ≈ 业务侧的“调度 + 限流中枢”
4 状态管理与协同(State Management)
全链路状态可视
WES 维护
- 单据状态机
- 作业单状态机
- 任务状态(初始状态、 待分库存、 待下发、 待推实操、 待实操、 完成、 回滚)
价值
-
避免“任务丢失”
-
支持失败重试、补偿
-
支持暂停 / 恢复
📌 常见实现
-
状态机
-
事件驱动(状态变更触发下一步)
5 异常处理与恢复
真实生产环境最重要的一点
异常包括:
-
设备故障
-
通道堵塞
-
WCS 超时 / 无响应
-
任务执行失败
WES 的职责
-
超时检测
-
重试 / 改道 / 降级
-
人工介入(挂起、人工确认)
-
任务回滚或补偿
2.2、WES 调度能力的“非核心但关键”能力
1 执行监控 & 可视化
-
实时任务看板
-
吞吐量 / 延迟
-
堵点识别
2 策略规则动态配置能力
-
调度策略可配置
-
限流参数动态调整
-
不同业务场景复用
3 与 WCS 的协议适配
-
REST / MQ / TCP
-
幂等、ACK、重试
2.3 WES 业务功能
这部分功能是兼管了 WMS 的功能。
2.3.1、 基础数据管理:系统的基石
任何智能系统的运行都依赖于准确、完整的基础数据。:
- 货主与商品管理:支持货主信息的新增、编辑、删除、查询、导入/导出及有效性管理。商品(SKU)管理同样全面,支持一个产品对应多个编码,并可维护商品图片、规格、供应商、ABC分类等丰富信息。
- 容量与条码管理:维护商品在不同容器(货架库位、料箱料格)中的容量数据,系统可根据上架记录自动更新。同时管理商品条码的匹配关系,确保扫描识别的准确性。
- 容器与库位管理:对货架、料箱、料格、库位等物理存储单元进行全生命周期管理,包括类型定义、有效性控制和可视化布局编排。
2.3.2、 入库管理:高效收货上架
WES支持多种灵活的上架模式,适应不同业务场景:
- 多种上架模式:
- 直接上架:适用于已知明确上架库位的场景,支持整托、整箱上架。
- 理货上架:针对零散商品,系统推荐合并库位,先下架整理再上架,以优化存储空间。
- 在线/离线工作站操作:提供图形化的工作站界面(在线)和PDA操作(离线),引导操作员完成扫描容器、商品、库位、确认数量等步骤。
- 入库单管理:支持手动创建、导入或接口下发入库单,并对入库单进行提交、取消、关闭等全流程状态管理。
- 策略与容错:支持混放规则控制(控制不同商品或批次能否共存)、商品容量计算,并提供了“无合适库位”时的处理机制,如将商品转移至暂存区并自动生成返库上架单。
2.3.3、 出库管理:智能拣选与分播
出库是WES的核心价值体现,其功能设计旨在最大化拣选效率和准确率:
- 出库单与波次管理:支持出库单、集合单的创建、提交、指定工作站、优先级调整、取消及缺货发货。系统支持静态组波(T+1场景,预先组单)和动态组波(即时订单,实时优化),提升订单聚合效率。
- 智能化拣选作业:
- 任务调度:根据策略自动将出库任务分配至最优工作站,并调度对应的货架或料箱到站。
- 多样化拣选模式:支持按单拣选、按箱拣选。系统提供清晰的实操界面,引导操作员从指定库位拣选商品放入指定分播墙槽位,支持扫描校验、缺拣记录、后置绑箱等操作。
- 可视化辅助:工作站界面显示库位/槽位状态(绿色有单、红色缺货等)、商品图片、拣选数量,并可与电子标签灯(PTL)联动,实现“灯光拣选”。
- 策略中心深度配置:出库环节拥有丰富的可配置策略,如拣选库存分配算法(效率优先/清库存优先)、缺货提交方式、扫码校验严格度(不需要/需要/逐件扫描)、槽位容量比例、临近超时与槽位超时释放策略等,实现高度定制化。
2.3.4、 库内管理:精益化库存运营
为保障库存准确性及优化库内结构,WES提供了强大的库内管理工具:
- 在线盘点:支持明盘与盲盘,可按商品、按容器、动碰(按日期或单据)、全盘等多种方式创建盘点单。操作员在工作站直接清点,系统自动比对差异并记录原因(盘盈/盘亏),可自动生成库存调整单或复盘单。
- 在线理货:用于整合零散库存。操作员可申请理货任务,系统将需要合并的商品从分散的库位下架至工作站槽位,再引导其上架至合并后的目标库位,过程中自动处理多货、少货等异常。
- 库存调整:支持通过库存调整单、批次属性调整单、货权调整单,对系统库存数量、批次属性、货主归属进行手工修正,处理盘点差异、理货差异等异常。
2.3.5、 库存可视化与查询
WES提供多层次的库存视图,助力精细化管理:
- 二级库存:按“仓库+库区+货主+商品+批次属性”维度聚合显示库存总量。
- 三级库存:细化到“容器+库位”级别,可查看每个具体储位上的库存详情、锁定数量及被出库单占用的数量。
- 实时库存占用查询:实时追踪出库单对二、三级库存的占用情况。
- 库存日志:记录所有库存变化流水,便于追溯和审计。
2.3.6、 策略与配置中心:系统的控制台
WES的强大之处在于其高度的可配置性,用户可以通过策略中心和配置中心精细控制系统行为:
- 策略中心:以规则引擎的形式,管理出库、入库、盘点、打印等各环节的策略。例如,设置出库单初始优先级、是否自动提交、缺货处理方式、混放控制规则、装箱清单打印策略等。支持为不同条件(如货主、单据类型)设置不同的规则和优先级。
- 配置中心:提供大量开关和参数配置,如是否启用逐件扫描、是否允许取消作业中的出库单、提前离站设置、盘点任务上限、接口反馈内容等,满足不同仓库的个性化流程需求。
2.3.7、 系统管理与集成
- 用户与权限管理:支持多用户账号、角色权限控制,保障系统安全。
- 多语言与外观:支持中、英、日等多语言包,并可自定义系统LOGO和主题。
- 丰富的API接口:提供了与上游业务系统(如ERP、WMS)集成的完整接口集,涵盖商品同步、入库单/出库单/盘点单的下发与反馈、库存查询、调整单同步等,实现业务流程无缝对接。
- 工作站管理:支持在线工作站、PDA等多种终端设备的配置与管理。
三、WES 应用架构
-
展现层:面向各类用户的交互窗口,包括管理员使用的WEB后台、自动化设备接口(DEVICE)和现场操作员手持的PDA。
-
应用层:WES系统的大脑与核心,所有业务逻辑、调度规则。
-
数据层:系统的记忆中枢,使用MySQL存储核心业务数据,Redis支撑高并发缓存,MongoDB可能处理日志等非结构化数据,Drools则独立管理策略规则。
-
基础设施层:系统的躯体与感官,基于Docker实现容器化部署,并通过一系列监控工具(如Prometheus、Grafana)保障系统健康运行。
中间件(如RocketMQ、XXL-JOB、动态线程池)贯穿各层,负责异步通信、定时任务调度等,保障系统高可用与可扩展。

四、WES 核心链路
4.1、出库正向链路
我们先聊聊出库核心链路。
阶段一:订单创建与初始状态 (待分配)
-
起点:上游系统(如OMS)下发出库单,WES创建订单,初始状态为
待分配。 -
核心操作:库存预占。此时并不实际扣减库存,而是预先锁定库存量,防止超卖,保证了库存数据的一致性。
阶段二:库存分配与策略决策 (待分配-> 待下发)
这是WES智能化集中体现的关键环节。
-
核心操作:库存分配。系统需要决策“从哪个或多个货位,拣选哪个批次的商品来满足此订单”。
-
策略规则引擎介入:分配过程会调用规则引擎(如Drools),基于预设策略进行计算,例如:
-
货位优先级:就近原则、先入先出原则。
-
效率最优:减少拣货员行走路径、最大化单车载货量。
-
库存优化:优先消耗临期库存。
-
-
结果:分配成功后,订单状态跃迁至
待下发。
阶段三:任务组单与指令下发 (待下发-> 执行中)
此阶段的核心是 “任务组单”,目的是提升现场作业效率。
-
核心操作:系统不会立即处理一个订单,而是将一段时间内多个订单的拣货任务进行聚合与优化。
-
将去往同一区域或相邻货位的任务合并,生成一张高效的拣货波次。
-
这就是图中 **“任务树”** 概念的应用,将多个订单任务聚合为一棵更优的任务树。
-
-
指令下发:组单完成后,WES通过WCS/RCS接口将具体的拣货指令下发给自动化设备(如亮灯拣选、AGV调度),订单状态变为
执行中。
阶段四:任务树驱动与现场执行 (执行中)
订单进入物理执行阶段,由 **“任务树”** 驱动,将一个订单分解为一系列有序的原子任务。
-
任务分解:
-
生成拣货任务:拣货员或设备根据指令到指定货位拣选指定数量的商品。
-
生成搬运任务:AGV等设备将拣选出的商品搬运至复核台。
-
生成工作站复核任务:系统提示复核员扫描商品,确保拣选的准确性。
-
生成打包任务:复核无误后,系统生成打包任务,并打印面单。
-
-
状态管理:每个子任务都有其自身的状态(如
待执行、执行中、已完成),WES需要实时追踪整个任务树的完成进度。
阶段五:最终确认与完成 (执行中-> 已完成)
-
核心操作:当订单的所有子任务(拣、搬、复、包)都完成后,系统进行最终确认。
-
库存扣减:此时才在数据库中进行实际的库存扣减,确保账实相符。
-
状态跃迁:订单状态最终变为
已完成,并向OMS等上游系统回传完成信息。

4.2 入库正向链路
整个入库流程可以被清晰地划分为三个阶段,体现了从信息录入到物理上架的完整闭环:
-
【信息预置与准备阶段】:单据创建与模式选择。
-
【物理收货与绑定阶段】:货品实收、质检、并与系统容器绑定。
-
【智能上架与执行阶段】:系统调度资源,完成货品上架,按单、按箱将上架信息反馈给上游系统
阶段一:信息预置与准备
此阶段是流程的起点,核心是 **“单在先,货在后”** ,在系统中搭建好作业框架。
-
起点:上游服务(如ERP或采购系统)创建并向WES 下发入库单,单据中包含了预期的货品、数量等信息。
-
WES响应:WES 保存入库单,建立本次入库作业的任务主干。
-
现场就绪:操作员在STATION工作站上上线,并开启直接上架业务模式。此模式意味着收货后可直接规划上架,无需暂存再处理,是提升效率的关键设置。
阶段二:物理收货与绑定
此阶段是实物与信息首次交汇的关键环节,核心是 “验明正身,建立关联”。
-
PDA扫描收货:操作员使用PDA(手持终端)选单,找到对应的入库单。
-
质检与确认:选合格(完成质检操作),然后选商品、选批次、确认上架数量。这个过程确保了入库货品的准确性。
-
关键的系统逻辑:
-
按规则校验:此处会调用策略规则引擎,校验批次、效期、包装规格等是否符合预设规则。
-
建立绑定关系:系统执行 “入库单与容器的绑定(1:n)” 。这是核心概念,意味着一张入库单的货品,可以放入一个或多个物理容器(如托盘、料箱)。绑定后,库存进入“收货暂存区”,这是一个逻辑库存状态,表示货品已接收但未确定最终货位。
-
阶段三:智能上架与执行
此阶段是WES作为“调度大脑”能力的集中展现,核心是 “智能决策,驱动执行”。
-
任务生成:工作站生成上架申请单,WES随后生成上架任务。这里的“任务”包含了“将哪个容器,放到哪个货位”的完整指令。
-
智能调度与下发:
-
WES将任务下发给WCS(仓库控制系统)。
-
WCS通过调度算法服务,计算最优路径,调度**AGV(小车)** 前往工作站接取容器。
-
-
任务执行与监控:
-
小车搬运容器前往目标货位,并异步上报位置,实现全程可视化监控。
-
WCS推送实操指令到工作站,指引操作员(或自动化设备)完成最终放置动作。
-
操作员确认后,上架完成,反馈信息。
-
-
最终确认与反馈:
-
小车离站,资源释放。
-
系统通过反馈定时器,按单或按箱批量、异步地反馈结果给上游服务,并更新单据状态为完成。这种异步反馈机制避免了阻塞主流程,提升了系统整体性能。
-

4.3盘点正向链路
4.4 理货正向链路
4.5 逆向链路
五、三级库存模型
5.1、WES 的多级库存模型是怎样的?
WES 的库存模型分为三层:
- 一级库存
- 二级库存
- 三级库存
粒度逐件变细。一级库存是全仓库存,商品维度,二级库存是库区+商品+批次+包装,三级库存则细粒度到库位(料箱、料格),与具体的存储位直接关联。

5.2、一级库存的优势与弊端?
先说优势,对于全仓库存的可视化,其实是简单了许多,其次上游调取库存快照时,减少了计算。
劣势也很明显:
- 一级库存是粒度较大,如果是大库存商品并且是热点商品的话,即使是在 B 端也很容易变成热点行,高并发下,出现性能问题。
- 库存的级数越多,要保证数据一致性的难度就越高,代价也越高。
- 死锁的风险也会增大

5.3、重构中如何做取舍?
工作越久,越觉得软件开发中,没有银弹,方案本身没有好坏,只有适合与不适合,大多数时候我们都在做权衡(Trade-Off)。
对于 WES 来说,数据一致性和稳定性比报表类的需求其实是更核心的,一级库存固然有一定优点,但都可以通过二级库存聚合计算得到,而且一级库存的使用场景都很低频,去掉一级库存完全是能满足业务需求的。
去掉一级库存的优势就很明显了,数据一致性的维护更简单了,死锁风险降低,代码层面也会降低复杂度,热点行的发生频率也会下降一个级数。
所以最终还是去掉了一级库存。
5.4、为什么要有二级库存?
二级库存关注的维度是单库区商品批次维度,那我们先假设去掉二级库存,那会有哪些问题?
- 性能: 在一些需要库区视角的场景中,比如出库单缺货时无法快速判断,影响补货时效,因为三级库存是分散在具体库位的,需要去三级库存表查找目标库存列表,并做聚合计算,尤其在某些仓储场景下,sku 种类很少,那要查询的三级库存数量会很大,对三级库存表是一个很大的负担。
- 逻辑耦合:库存决策(是否满足订单)与执行细节(从哪个货位取货)混杂,增加代码复杂度
- 另外二级库存与我们的出库调度模型有关联,减少二级库存会让上游的出库单与调度任务直接关联,会缺少灵活性。
基于上述原因,我们最终还是保留了二级库存。

5.5、库存扣减的幂等性如何保证?
我们有库存流水表,会有幂等键,二级库存是 出库作业类型 + 作业单 ID + 明细 ID,三级库存扣减是出库作业类型 + 任务 ID。
5.6、MySQL 库存扣减的性能瓶颈与优化思路?
1 性能瓶颈

MySQL 库存扣减其实本质上是热点行更新的问题,当多个并发事务同时尝试更新同一行热点数据时,可能会导致锁竞争和冲突。这会增加事务的等待时间和冲突概率,导致性能下降,并可能引发死锁问题。
具体来说,可能会产生以下问题(参考了网络上的内容,学习一下):
- 锁竞争,热点数据的更新是通过update语句进行的,而update是需要给记录增加排他锁的,这就会导致大量的请求被阻塞。降低整个系统的吞吐量。
- 占用数据库连接,当有大量的update语句,因为要修改同一条记录而被阻塞的时候,他们持有的数据库连接是不会释放的,而数据库连接又是有限的,所以会导致连接数不够,进而影响整个系统的吞吐量及可用性。
- 耗尽数据库CPU,大量锁等待,就会导致大量的自旋,多个线程就会不断的尝试获取锁,CPU就需要不断的执行自旋操作,并且需要做死锁检测,消耗大量CPU时间。并且在这个过程中,操作系统也需要频繁的进行线程上下文的切换,这个过程会导致CPU时间片的浪费。
- 死锁风险,在高并发的情况下。由于数据库需要频繁定位和更新这些特定行,可能会增加锁竞争和死锁的风险,影响并发性能。
- 索引维护开销大,频繁地更新热点数据,不仅会导致数据的变化,还可能导致相关索引的频繁维护,这可能会增加数据库的开销,导致性能下降。
- 主从不一致,热点数据的频繁更新,如果在主从复制出现延迟的情况下,就会放大数据不一致的概率。
2 优化思路
这类问题的解决思路可以分为3类,分别是排队、拆分以及批次执行。
思路 一:排队
排队方案要么是加锁,要么是单线程执行。
如果能使用Redis的话,那么就可以利用他的高并发、单线程特点来解决这个问题。
如果是使用数据库的话,可以使用优化版的云MySQL,腾讯云 MySQL 和阿里云 MySQL。
他们都做了二次开发优化,系统会自动探测是否有单行的热点更新,让同一个热点行的更新语句,在执行层进行排队。这样的排队相比update的排队,要轻量级很多,因为他不需要自旋,不需要抢锁。
思路二:拆分(用得不多)
类似分段锁机制,将一次扣减分散到不同的库表中进行,但可能存在碎片库存。
思路三:合并
把多个UPDATE合成一个UPDATE, 比如一个用户,有10个占用库存请求,每次占用1个,那么就可以提供一个批量占用的接口,让上游一次性把10个占用合并一起,这样数据库只需要做一次更新就行了
局限性:不是所有请求都可以合并的,有些场景,如电商的秒杀,用户需要很快的知道反馈,而批量执行就需要有个窗口来聚合,用户是不能接受这种等待窗口的。在一些异步链路上,可以用这种方案。

5.7、学习互联网电商高并发库存扣减的思路
1 两种方案
主流的有两种方案:
- 数据库扣减:
- redis 扣减:使用lua 脚本,先判断库存呢是否足够,足够再扣减
2 Redis 库存的正确扣减
秒杀因为是一个高频的并发库存扣减的场景,所以,如何提升库存扣减的性能,并且保证他的准确性,这是一个在秒杀业务中极其重要的课题,稍有不慎就会带来超卖、少卖等问题。另外还需要关注一下对账的系统设计。
具体的方案设计如下图:

3 Redis保存库存的时候,如何避免被Redis清理掉?
考的其实是 Redis 的内存淘汰策略。
用 volatile 相关策略,这样只有设置了超时时间的才可能被淘汰。
阿里云上的Redis的默认的淘汰策略是volatile-lru。
腾讯云默认是noeviction。
六、出库效率优化
6.1、出库链路耗时在哪里?
如果想优化出库正向链路,那么首先要知道耗时在哪些地方,这样才能对症下药。
先回顾一下出库链路图:

类似操作系统中磁盘 IO花费时间一般由 3 个部分组成:
- 寻道时间
- 旋转延迟
- 数据传输时间
这三者的时间都蛮长的,尤其是两者。为什么呢?因为涉及到物理移动。
在出库链路中,耗时最长的也是如此,所以我们主要从这个方面入手。
优化主要从两个方面来讨论,业务上优化以及技术上的优化。
6.2、出库调度正向链路业务优化
1 热度移位
在 WES(仓储执行系统)中, 热度移位功能是一项智能的库存动态调整策略。其核心目标是让高频次出入库的商品(热销品)尽可能地靠近仓库的拣选工作站或出库区域,从而显著减少拣货员或自动化导引车(AGV)的行走或移动距离,最终提升整体仓储运营效率。
看一下热度移位前后对比图,应该比较清晰一些:

我们从图中可以看到,热度移位后,高频商品(红色部分)都移动到拣选工作站附近了。
热度移位的目的本质是为了减少搬运时间,从而大幅提高出库效率。
2 超 A 不返库
智能仓的存储区是一个三维空间,热度移位更侧重水平坐标的优化,超 A 不返库则更侧重垂直坐标的优化。
说的有点抽象,我简单解释一下。仓储区域是一个个的货架,货架分为多层,每一层又分为多个库位,最下层是缓存层,料箱容器如果放在货架上层,则需要从上层搬到下层,再从下层搬运到拣选工作站。
超 A 不返库指的是高频超 A 商品放到货架最下面一层,也就是缓存层,减掉料箱从货架上层搬运到货架下层的流程。
文字可能不太好理解,我们看张图示意图吧:

3 FlowPick 单品单件
我们组负责的一些日本海外仓,比如 Eqseek,单据类型比较特殊,单品单件订单占比近80%,为提高拣选效率,可以一次入站完成更多件数,提出flowpick产品化方案。
当然国内直播电商也有类似场景,比如直播间大主播,观众下单,大多是单品单件订单。
WES对可聚合订单做标记,交由算法组,让算法把一批订单聚合分配到同一个 flowpick 槽位中。
这样能大幅减少WES 调度和下游搬运次数。

4 进站提前上报
小车搬运容器即将进入工作站时,会减速,达到指定码点后,会上报到站消息。
WES 拿到搬运系统上报的到站消息后,会推送实操任务给 STATION。
我们做了提前上报功能,小车在即将进站前,到达指定码点时,就提前上报到站消息,让 WES 处理并推送实操任务,这样相当于做了并行处理:
- 小车从指定码点到真正进站
- WES 接收到站消息并推送实操任务
这样也减少了出库链路时间。
如下图所示:

5 提前离站
原先的流程是拣选、封箱、点击全部分播完成后车才走,但其实拣选完之后,车就可以走了。
提前离站我们做了开关,在拣选到最大值后,车提前离站,下一个车可以直接进站。
这样我们又减少了一部分链路耗时。
这里我也画了一张流程图,如下所示:

6 预出库
那什么是预出库呢?我先聊一下问题背景,其实前文也聊了场景:
料箱场景下的出库分为两段搬运:
- C56 机器人: 将料箱搬运至缓存位。
- 青鸾机器人: 将缓存位的料箱搬运至工作站。
为了提高拣选效率,我们探讨了能否将第一段搬运(C56 搬运至缓存位)提前完成,即实现“预出库”。
在料箱出库场景中,物料搬运分为两步:
-
C56 机器人: 将料箱搬运至缓存位。
-
青鸾机器人: 将缓存位的料箱搬运至工作站。
为了提高拣选效率,我们探讨了能否将第一段搬运(C56 搬运至缓存位)提前完成,即实现“预出库”。
我们提出了两种预出库方案:静态预出库和动态预出库。
1). 静态预出库 (Static Pre-Outbound)
| 适用场景 | 客户在一天中某个特定时刻集中下发大部分订单。 |
|---|---|
| 方案描述 | 系统根据已下发订单所需的库存,提前将料箱搬运至缓存位。 |
| 优点 | 简单易实施: 一天只需计算一次。 |
| 缺点 | *需要严格控制订单的出库上墙顺序。 * 对缓存位占用较大。 |
2). 动态预出库 (Dynamic Pre-Outbound)
| 适用场景 | 提升实时拣选效率,优化缓存资源占用。 |
|---|---|
| 方案描述 | 工作站虚拟出部分槽位用于分配“预出库订单”。 这些预出库订单仅执行第一段搬运(搬至缓存位)。 当实际订单完成后,对应的预出库订单才会转移到实际分播墙槽位上。 |
| 优点 | 缓存位占用少,资源利用灵活。 对实际拣选效率提升显著。 |
| 缺点 | 需要实时计算和持续监控。 对系统资源消耗较大。 |
七、WES 调度模型
我在前文聊过出库链路的大致链路流程。
但其实真实的出库调度链路远比这复杂,不仅涉及到库存模型、多样化的策略规则、配置、调度算法、热度算法,还涉及到单据任务调度以及相关的单据状态机、作业单状态机、任务状态机等等。
本文我将聊聊WES 的单据任务调度。
WES 这边的调度模型基本遵循三层:
- 单据层:单据和单据明细
- 作业单层:作业单和作业单明细
- 任务层:包含命中的三级库存信息、容器、货架、库位
从上到下,都是一对多的关系,以出库链路来说,一个出库单对应多个出库作业单,一个出库作业单对应多个出库任务。
7.1、为什么要设计三层调度模型?
在一些小仓场景,仓内商品库存比较少,作业场景比较简单,其实作业单这一层是可以省略的,但我司的客户群体基本都是大仓,单仓过万平方,多库区,库存条目数过百万。如果只有调度任务这一层的话,无法精细化管理。比如出库单无法很好地表示库区维度的作业情况,哪些库区是有货的,哪些库区是无货的,哪些库区在作业了,哪些库区还没开始作业。
除了业务场景的考量,其实在模型含义上,直接使用上游下发的出库单来管理仓内作业流程,业务领域有些混淆了。出库单是上游的概念,出库单定义‘要什么’,而作业单定义‘怎么干’,关注的具体的执行层。
7.2、单据、作业单、任务的状态流转
该部分信息有点涉敏,我简单聊聊。
出库单是上游下发的单据,所以肯定有初始状态。
提交出库单后,会预占二级库存并且生成出库作业单,出库作业单也有初始状态。
出库作业单会经过复杂计算,并传给调度算法服务,最终 WES 会根据出库作业单与算法结果分配工作站、预占三级库存,并生成出库任务。
同样的,出库任务也有初始状态。
定时任务会流转,最终下发给下游的车辆调度系统,下发成功后,任务状态流转。
车辆搬运中会不断上报任务调度实体,也就是小车的位置信息,到站后,调度任务状态变更。
推完实操后,调度任务会变更为实操中。
实操结束后,会变更为完成。
7.3、调度的搬运优先级
在履约链路中,会存在履约时效性的概念,在仓配场景中,简单来说就是,如何及时地把货物出库并配送到买家手中。
那上游下发的单据,可能就会带有履约时效性的信息,比如:
- 单据优先级:优先级越高,调度任务的优先级也会根据权重提高优先级
- 期望发货时间:结合规则策略,动态根据期望发货时间提高优先级
除了上游单据自带信息,在 WES 内部也会根据一些策略,去动态提高任务优先级,比如:
- 按出库单持续时间:一个单子执行的时间越久,尾单部分,优先级就需要越高
- 按剩余拣选任务数:有的时候,上游希望剩余拣选任务数达到阈值后,能提高优先级。
7.4 库存分配优先级
对于多库区精细化作业,需要具备按库区优先级来分配库存,比如存在 A、B 、C 库区,希望先把 A 库区的库存先出掉,再出 B、C 库区的库存。
同样的,这个能力也是需要提供规则策略动态配置能力的。
八、与第三方系统交互数据一致性保障
8.1、WES 与第三方系统数据不一致困境
首先说一下智能仓储系统的场景:智能仓储系统的核心链路涉及多个系统,但是对于数据最终一致性有要求,且部分场景需要提供补偿机制。
我们需要与诸多二方系统和三方系统对接,比如:
1、车辆调度系统,可能就会遇到:
- 车不来:下发调度信息的时候,消息发送失败,导致车辆不来
- 来错车:下发调度信息的时候,消息乱序,导致来错车
- 车不走:下发车辆离站消息的时候,消息发送失败,导致车辆不走
2、与算法服务对接
- 离线任务下发失败
- 调用算法计算热度
3、与外设系统交互,可能会遇到:
-
灯不亮:
- 发送亮灯消息的时候,消息发送失败,导致外设系统未接收到消息,灯不亮
- 外设系统与物理设备交互,调用相关接口失败
-
灯不灭:
- 发送灭灯消息的时候,消息发送失败,导致外设系统未接收到消息,灯不灭
- 外设系统与物理设备交互,调用相关接口失败,灯不灭
-
亮错灯:
- 发送亮灯消息的时候,消息乱序,导致外设系统亮灯错乱
4、与打印系统交互,可能会遇到:
- 没打印:接口调用失败,导致单据打印失败
5、与上游系统交互:
- 各种单据的实操结果未正常反馈上游
- 出库单按单反馈
- 出库单按箱反馈
- 入库单按单反馈
- 入库单按箱反馈
- 盘点单按单反馈
- 库存调整单按单反馈
- ……
6、与基础数据系统交互
- 货架热度计算结果更新失败
- 料箱热度计算结果更新失败
- 容器位置更新

这些场景无法使用本地事务实现,因为是分布式系统。有些场景也不能纯用 MQ 的消息事务实现,因为 RocketMQ 事务消息重试机制不灵活。
8.2、关于重试逻辑的思考
一个健壮的系统是需要考虑关键节点的稳定性的,以与外部系统交互这种节点来讨论,我觉得核心的意识是不相信第三方系统,无论是数据的获取还是推送。
所以需要实现重试逻辑。
但二方系统、三方系统有许多,如果在每一个节点都去写重试逻辑,那么重试逻辑就会变得不可复用。比如说:
- 重试次数怎么设置?
- 每次重试的间隔如何考虑?
- 重试逻辑是否影响主线程,需要异步化吗?
- 是否考虑加降级呢?
- 能不能加告警?
- ……
8.3、抽象、复用与便捷
考虑以上问题后,我在阅读了转转和得物的关于重试组件的技术博客后,开始有了新的思考。是不是可以抽取出业务需求,自定义一个 springboot starter,用户只需要引入这个 maven 包,做一些简单的配置和适配,就可以做到关键节点的自动重试呢?
很幸运,我在网上找了一些类似的教程和代码,结合得物、转转的技术博客,实现了这个组件。
我们可以基于SpringAOP来实现,将需要重试的逻辑抽取成 public 修饰的方法,在这个方法上加上一致性注解。
拦截所有加了一致性注解的方法,封装为一个重试任务,持久化到数据库中,再通过反射去执行这个任务。

8.4、如何设计自定义注解
我们如果想基于反射来做,那注解必须要有反射相关的信息,另外还要有执行间隔、延迟时间、告警相关
降级相关。
- 任务名称:默认取方法全限定名,因为想使用反射来执行
- 执行间隔:任务执行间隔
- 初始延迟时间
- 告警表达式
- 告警类
- 降级类
- ……
8.5、重试任务执行流程
如下图所示:

8.6、如何自定义任务查询逻辑
任务失败重试是通过定时任务调⽤ taskScheduleManager.performanceTask() ⽅法来实现 的,底层逻辑就是根据条件从数据库中查询出来失败的任务,然后判 断是否需要重试,执行后续逻辑。
在这个过程中,根据条件查询失败的任务,这⾥的条件允许⼀定程度的⾃定义。默认情况下⾏为 是: 每次查询当前时间 - 1⼩时 时间范围内的1000条失败的记录。
如果想要更改此逻辑,可以自定义查询类名并继承查询接口,然后在 yml 中配置全路径类名,组件接入启动时会根据自定义配置类来反射获取自定义查询配置信息。

8.7、指数退避重试
指数退避重试是一种智能的容错机制,其核心思想是当操作失败后,重试的等待时间随着重试次数的增加而呈指数级增长,并通常会引入随机扰动。它能有效防止因频繁重试导致的系统压力激增,是构建稳定分布式系统和网络应用的关键策略。
| 重试次数 | 基础延迟计算(示例) | 实际等待时间(含抖动) | 说明 |
|---|---|---|---|
| 第1次重试 | base_delay * (2^0) = 1s |
1秒 ± 随机时间 | 初始快速重试,希望问题已瞬时恢复 |
| 第2次重试 | base_delay * (2^1) = 2s |
2秒 ± 随机时间 | 延迟加倍,给系统更多恢复时间 |
| 第3次重试 | base_delay * (2^2) = 4s |
4秒 ± 随机时间 | 继续指数增长,进一步退让 |
| 第n次重试 | base_delay * (2^(n-1)) |
计算结果 ± 随机时间,但不超过 max_delay |
避免等待时间无限增长 |
| 在计算下一次执行时间时,可以按照这个指数退避重试,但一般我们设置的重试次数都很小,所以与线性重试差距不大。 | |||
8.8、降级逻辑设计
有时候我们希望重试失败之后进行降级处理,所以注解中要支持定义降级类。
如果配置了降级类,并且超过了重试次数阈值,就调用降级逻辑。
具体的实现方式就是通过反射调用指定降级类的同名方法,方法参数要与原方法一致。
8.9、告警逻辑
告警逻辑可以自定义,在注解上可以配置自定义告警类,如果触发告警规则,则通过反射调用告警类的告警方法。
因为告警可能会比较耗时,所以做了异步化,避免影响主线程。
8.10、如何设计重试任务表
数据模型:argo_task(任务表)
| 字段名 (Field Name) | 数据类型 (Data Type) | 允许空值 (Nullable) | 默认值 (Default) | 注释 (Comment) |
|---|---|---|---|---|
| id | bigint | NOT NULL | AUTO_INCREMENT | 主键自增 |
| task_id | varchar(500) | NOT NULL | - | 用户自定义的任务名称,如果没有则使用方法签名 |
| task_status | int | NOT NULL | 0 | 执行状态 |
| execute_times | int | NOT NULL | - | 执行次数 |
| execute_time | bigint | NOT NULL | - | 执行时间 |
| parameter_types | varchar(255) | NOT NULL | - | 参数的类路径名称 |
| method_name | varchar(100) | NOT NULL | - | 方法名 |
| method_sign_name | varchar(200) | NOT NULL | '' | 方法签名 |
| execute_interval_sec | int | NOT NULL | 60 | 执行间隔秒 |
| delay_time | int | NOT NULL | 60 | 延迟时间:单位秒 |
| task_parameter | varchar(200) | NOT NULL | '' | 任务参数 |
| performance_way | int | NOT NULL | - | 执行模式:1、立即执行 2、调度执行 |
| thread_way | int | NOT NULL | - | 线程模型 1、异步 2、同步 |
| error_msg | varchar(200) | NOT NULL | '' | 执行的error信息 |
| alert_expression | varchar(100) | YES | NULL | 告警表达式 |
| alert_action_bean_name | varchar(255) | YES | NULL | 告警逻辑的执行beanName |
| fallback_class_name | varchar(255) | YES | NULL | 降级逻辑的类路径 |
| fallback_error_msg | varchar(200) | YES | NULL | 降级失败时的错误信息 |
| shard_key | bigint | YES | 0 | 任务分片键 |
| gmt_create | datetime | NOT NULL | - | 创建时间 |
| gmt_modified | datetime | NOT NULL | - | 修改时间 |
-
主键 (Primary Key):
PRIMARY KEY (id)。 -
唯一索引 (Unique Key):
UNIQUE KEY uk_id_shard_key (id, shard_key) USING BTREE。这是一个复合唯一索引,确保了id和shard_key组合的唯一性,常用于分库分表场景。 -
存储引擎与字符集 (Storage Engine & Character Set):
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci。使用InnoDB引擎,字符集为支持更广范围字符(如emoji)的utf8mb4。
九、WES 内部系统数据一致性保障
我在现司的很长一段时间里一直被现场问题工单 oncall 弄得痛不欲生,大概有半年多,我司很多第三方仓,尤其是海外仓,跨时区工作,经常半夜打我电话,一起床又是一个通宵。坦诚来说,工作这么多年,在我司的前半段时间工作,是我职业生涯中最痛苦的一段时间,捂头哈哈~
这段经历让我切身体会到:当核心履约链路缺乏系统性设计时,稳定性问题最终一定会以“人工成本”的形式被偿还。
后来,在一位在公司深耕十年的前辈建议和引荐下,我转入产品线团队,参与 WES 系统的重构工作,也正是在这一过程中,我开始系统性地审视出库链路,并逐步引入 Saga 思想来解决长期存在的履约一致性问题。
收获良多,我很感谢他。
在仓储系统中,出库履约是一条跨系统、跨资源、长生命周期的业务链路,涉及到状态机、库存模型、调度模型、规则配置等等。本文结合我过往在 WES(Warehouse Execution System)中的工程实践,分享出库链路中落地 Saga 的思考、演进过程与经验总结。
9.1、仓储业务名词解释
为了便于不同业务领域背景的朋友能理解本文的一些内容,加了一些名词说明。

1. 出库单
出库单本质上是上游下发的单据,比如你在淘宝上买了 2 个牙刷,3 件毛巾。那上游(可能是 OMS、WMS)会下发一个出库单到 WES 来,告知 WES 我现在要出货配送 2 个牙刷、3 件毛巾。出库单就是用来表达要出库的商品信息。
在数据模型上,出库单分为主体和明细。
2. 出库作业单
出库作业单某种层面上是根据出库单做的一层解耦单据,将具体的执行层与上游下发的单据层隔离开。
另一个维度来理解的话,是按库区维度对出库单做了一次拆单。
出库作业单单也有主体和明细。比如说上文的出库单,可能会分裂成两个出库单,A 库区出库 2 件牙刷,B 库区出库 3 件毛巾。
3. 出库任务
出库任务业务含义上表示要从某个存储库位(货架库位、料箱、料格)中出库某个包装批次的商品。
WES 会对这个出库任务做一层转换,加上搬运调度层的含义,比如要把这个商品放到哪个工作站的那个槽位上去拣选,搬运的目的地是哪里。
4.二级库存
库区层面的库存,智能仓分为多个库区,二级库存是库区维度商品批次库存。
5.三级库存
库位维度的库存,实际的存储库存,比二级库存会更细粒度一些。
9.2、背景:为什么出库履约是一个“长事务”问题
在 WES 中,一次出库并非一个同步完成的动作,而是由多个阶段逐步推进:
-
上游系统下发出库单
-
根据规则拆分出库单生成出库作业单,预占二级库存
-
与算法服务交互,分配工作站槽位
-
与算法服务交互,分配三级库存,生成并下发搬运任务
-
将调度任务下发至下游搬运集群
-
小车到站推送拣选实操
-
实操反馈,扣减二三级库存,完结作业单、任务,小车离站

这一过程具备几个典型特征:
-
涉及多个本地事务
-
业务链路长,状态中间态多
-
允许失败、需要回滚和重试
-
对可用性要求高,不追求强一致
这使得传统的强一致分布式事务(如 XA / 2PC)在该场景下并不适用。
9.3、Saga 原理回顾(结合我个人的工程视角)
1. 什么是 Saga
Saga 是一种用于解决分布式长事务的问题模型,其核心思想是:
-
将一个全局事务拆分为多个可独立提交的本地事务
-
每个本地事务都对应一个补偿操作
-
通过正向执行与必要时的补偿执行,最终达成一致状态
Saga 的目标不是“永不失败”,而是:
在允许中间不一致的前提下,最终回到一个业务可接受的状态。
2. 编排型 Saga vs 协同型 Saga
Saga 通常分为两类:
-
协同型(Choreography):各参与方通过事件协作推进
-
编排型(Orchestration):由一个中心节点统一编排流程

在 WES 出库场景中,我们选择的是:
以出库单为业务目标的编排型 Saga
原因很简单:
-
WES 本身就是履约编排中枢
-
下游系统(如 WCS)不具备事务补偿能力
-
需要明确、可控的流程推进与回滚逻辑
9.4、WES 出库 Saga 的业务建模
1. Saga 的业务边界
-
Saga 实例粒度:出库单
-
Saga 目标:完成一次出库履约(成功或可控失败)
出库单天然就是一个 Saga 的生命周期边界。
2. 核心业务对象
-
出库单(Outbound Order)
-
出库作业单(Work )
-
出库任务(Job)
-
二级库存(L2,逻辑库存)
-
三级库存(L3,具体到库位库存)
-
工作站 / WCS
这些对象的状态变化,共同构成了 Saga 的执行轨迹。
9.5、Saga 的正向履约流程

几个关键点:
-
Saga 并不是同步完成,而是被定时器持续推进
-
每一步都是独立本地事务
-
任意一步失败,都不会影响已提交步骤的可见性
9.6、外部事件驱动的 Saga:工作站下线
在真实仓储环境中,资源变化是常态。
1. 典型场景:工作站下线
当某个工作站下线时:
-
已生成但未执行的任务不可继续
-
已预占的三级库存需要释放
-
原有作业规划失效,需要重新规划
2. 我们的处理方式
在 Saga 视角下,这不是“异常兜底”,而是:
一次由外部事件触发的补偿 + 重编排流程
具体行为:

Saga 在这里体现的是业务韧性。
9.7、Saga 的工程落地方式

1. 状态机 + 定时器:轻量级 Saga Driver
我们没有引入专门的工作流或 Saga 框架,而是采用:
-
状态机:描述“当前处于哪一步”
-
定时器:负责推进下一步执行
这种方式:
-
实现简单
-
易于调试
-
与业务模型高度贴合
2. 工作站维度的分布式锁
我们在工程上引入了:
以工作站为粒度的分布式锁
使用原则非常明确:
-
锁只用于并发收敛
-
不承担 Saga 一致性语义
典型使用场景:
-
生成任务
-
回滚任务
-
下发任务
-
处理工作站下线事件
并且严格遵循:
锁内逻辑原子化:先提交事务,再释放锁
9.8、Saga 的可观测性:任务流转树监控
Saga 最大的工程风险,不是失败,而是:
流程卡死却不可见。
为此,我们构建了一套任务流转树监控系统。
能看到什么?
-
一个出库单下:
-
出库明细的信息
-
分裂成了多少作业单
-
每个作业单生成了多少任务
-
每个 Work / Task:
-
当前状态
-
状态的业务含义
-
在当前状态停留的时长

这使得 Saga 从“黑盒流程”变成了“白盒履约”。
9.9、我们踩过的坑 & 为什么没选 TCC
1. 我们踩过的几个坑
-
把异常当成少数情况:在仓储系统中,异常本身就是常态
-
过早追求强一致:导致系统复杂度和耦合度急剧上升
-
缺乏可观测性:问题只能靠日志和人工猜测
2. 为什么没选 TCC
我们认真评估过 TCC,但最终没有采用,原因包括:
-
Try 阶段需要资源强锁定,不适合长时间履约
-
下游系统(如 WCS)不具备 Confirm / Cancel 能力
-
TCC 对接口侵入性极强,演进成本高
相比之下,Saga:
-
更符合“最终一致”的业务现实
-
对下游侵入小
-
更易与现有系统演进融合
9.10、总结
我们并不是“为了 Saga 而 Saga”,而是在解决 WES 出库问题的过程中,逐步演进出了一套 Saga 实践方案。
-
以出库单为业务目标
-
以状态机 + 定时器驱动
-
支持外部事件补偿
-
强调最终一致性与可观测性
这套方案没有追求概念上的完美,但在真实复杂业务中,稳定、可控、可演进,我觉得这,才是 Saga 在工程实践中的真正价值。
十、库存对账设计
十一、策略规则治理
WES 系统重构前,总代码行数接近八十多万,在这八十多万行代码中,既存在非常多的重复逻辑,又存在非常多的变化逻辑,前者可以通过不断地抽取公共逻辑来达到去重、瘦身的效果。
后者的治理则更为复杂一些,需要做很多抽象与统一整合,也就是策略规则。
坦诚来说,重构之前对规则引擎一点都不了解,自己也是在不断地接触和学习,扩宽自己的技术栈广度。回过头来,发现做重构确实让自己成长得很快。
1、正视复杂性:WES 业务规则的“多”与“变”

在深入技术选型之前,我们首先要清晰地定义问题。WES 作为仓储执行的入口,其核心复杂度在于其需要应对B 端多变且相互交织的业务规则。这些规则直接体现了不同客户、不同业务场景下的运营策略。
-
规则的维度多:仅出库环节,就涉及单据创建与提交策略(如自动拆合单、静态组波)、出库优先级调整策略(按发货时间、作业时长、剩余任务数动态调整)、库存分配策略(多库区优先级)、工作站作业策略(绑箱、槽位匹配与释放)等数十个可配置项。
-
规则的逻辑条件复杂:一个策略的生效往往是多重条件组合判断的结果。例如,“出库单初始优先级”策略,需要根据“出库单类型”来决定其初始的“优先级等级”和“优先级值”。这种
if...else if...else的链式判断,在传统编码中会形成难以维护的“代码沼泽”。

-
规则的动态性要求高:业务策略并非一成不变。新的客户需求、运营优化尝试都要求规则能够快速调整、热生效,而不需要重启服务或发布新版本。例如,快速调整“缺货提交方式”为“有货先作业”以应对临时的爆仓压力,必须是分钟级可配置的能力。
面对这种“多、变、杂”的业务规则,如果继续沿用硬编码的方式,会直接导致:
-
核心业务逻辑与规则逻辑耦合:业务代码中充斥着规则判断,可读性急剧下降。
-
维护成本高昂:任何微小的规则变更都需要开发人员介入修改代码、测试、上线,无法快速响应业务。
-
知识壁垒:业务规则散落在数十万行代码中,新人难以理解,业务专家(如资深的仓储规划师)也无法直接参与规则的制定与调整。
2、技术选型思考:为什么是 Drools?
基于上述痛点,我们明确了引入规则引擎的核心目标:将易变的业务规则从稳定的程序逻辑中解耦出来,实现业务规则的统一管理、可视化和动态配置。
在选型时,我们考察了多种方案:
-
脚本引擎(如 Groovy, Lua):灵活性高,但对于复杂的条件网络和规则推理支持较弱,需要自行实现优先级、冲突解决等机制,开发量和复杂度不可控。
-
自定义规则解析器(如 XML/JSON 配置 + 自研引擎):初期看似简单,但随着规则复杂度的提升,自定义的语法和引擎在性能、功能完备性上很难与成熟产品媲美,容易重复造轮子且后期难以维护。
-
商用规则引擎:功能强大,但存在商业许可成本,与我们的开源技术栈整合和自主可控的要求不符。

最终,我们选择了 Drools,主要基于以下几点考量:
-
成熟性与社区生态:Drools 是 JBoss 旗下的开源项目,经过多年发展,社区活跃,文档丰富,是一个非常成熟、稳定的企业级规则引擎解决方案。
-
强大的规则表达能力:其核心的 DRL(Drools Rule Language)语言专为规则设计,支持声明式的规则编写(
When-Then),天然契合我们的业务场景。它内置的 RETE 算法对于处理大量规则和数据有很高的效率。 -
与 Java 技术栈的无缝集成:WES 核心系统基于 Java 技术栈,Drools 可以非常方便地集成到 Spring 等主流框架中,降低了引入新技术栈的架构风险和学习成本。
-
“可进化”的架构潜力:Drools 不仅是一个规则执行引擎,其提供的 **KIE(Knowledge Is Everything)** 工作台概念,为我们未来构建图形化、Web 化的策略规则管理平台(正如策略中心)奠定了坚实的技术基础。
3、架构与落地:将业务规则抽象为 Drools 规则
选型只是第一步,如何将复杂的业务规则优雅地映射到 Drools 的规则模型中,是体现架构能力的关键。
1. 核心模型抽象
我们首先对策略中心的配置进行了领域模型抽象。每一个策略项(如“出库单初始优先级”)被抽象为一个 Policy聚合根,其下包含多条有序的 Rule。每条 Rule由 Condition(匹配条件)和 Action(结论数据)组成。这个领域模型与 Drools 的 Rule, LHS(Left Hand Side), RHS(Right Hand Side) 概念可以完美对应。
2. 动态规则加载
我们并没有让业务人员直接编写 DRL 文件,而是基于策略中心的数据库配置,在运行时动态生成 DRL 规则内容。系统启动或策略变更时,会从数据库拉取最新配置,通过模板技术将其转换为标准的 DRL 语法,然后加载到 Drools 的 KieSession中。这样,策略中心UI上的每一次“保存”操作,就相当于完成了一次业务规则的“发布”。
3. 规则执行与集成
在业务逻辑的关键节点(如“创建出库单”、“分配库存”前),我们将业务对象(如 Order对象)作为 Fact插入到 KieSession中,触发规则引擎执行。引擎会根据定义的规则顺序和优先级,对 Fact进行匹配和推理,并执行相应的 Action,修改对象的状态。执行完毕后,我们只需取出被规则修改过的对象,继续后续的业务流程即可。整个过程对核心业务代码几乎是透明的。
4、重构收益
引入 Drools 规则引擎后,我们获得了显著的收益:
-
架构清晰,职责分离:业务代码不再关心“如何判断”,只关注“做什么”,代码变得简洁、可读、易测试。
-
响应速度飞跃:常用规则基本百分百覆盖,现在可以由实施顾问或运维人员通过策略中心界面直接完成,无需开发介入,开新仓的时候,直接可以按需配置。