拓冰建站拓冰建站
首页 / 资讯中心 / 正文

自营物流系统站点排队列表与月台调度全流程设计实战

做物流系统的同学应该都有体会自营物流体系里站点排队和月台调度这两个模块看着不起眼实际做起来却很容易翻车。车辆排队排死了、月台明明空着但车进不来、高峰期车辆扎堆、司机等太久开始投诉、调度员在Excel里手工调来调去……这些问题我们做SLDS自营物流系统时基本都踩了一遍。这篇就把从零设计物流站点排队列表、月台管理全流程的思路、数据模型、调度策略和上线后的排查实录完整拆开讲适合正在做物流系统、仓库管理系统或者准备给自营运输体系加排队调度模块的同学参考。我写之前先说清楚一个认知排队列表和月台管理不是两个独立功能而是一条完整的车辆进站—排队—分配月台—装卸—离场链路。如果只做排队不做月台分配车辆还是在站门口乱停如果只做月台不管排队高峰期照样挤爆。这两块必须放在同一个状态机里设计否则后续联调会痛苦到怀疑人生。1. 先搞清楚排队列表和月台管理到底在解决什么问题1.1 一个典型的车辆到站场景先还原一个最普通的场景。某自营物流分拨站点每天有几百台车进场有从干线来的整车大车有从周边城市来的零担小车还有冷链、大件、逆向退货等不同货型的车辆。车辆到站之后司机需要先到站门口登记然后等调度安排月台卸货或者装货。注意这里有两个等第一个等是排队等月台第二个等是上个月台之后等装卸。很多人只关注第二个等其实第一个等才是瓶颈。月台数量固定的情况下车辆到站时间不均匀高峰期可能同时到二十台车但月台只有六个必然有人要等。等多久、按什么顺序轮、能不能插队、等太久了怎么办这就是排队列表要解决的问题。排队列表在SLDS里本质上是一个虚拟队列它不一定按物理排队顺序而是按规则排序。系统维护每台车的状态从到站登记开始进入队列直到分配月台、完成作业、驶离站点。这个队列是动态的优先级变化、预约时段、货型紧急度都会影响排序结果。1.2 传统先到先得模式为什么扛不住高峰早期的站点管理大多靠人工。司机到站后排队登记调度员按登记顺序安排月台。这套方式在日均几十台车的时候没问题一旦量起来就全乱了。第一个问题是登记信息失真。车辆到了但司机不排队先跑去吃饭等回来又想插队调度员记不清谁先到只能凭感觉处理。第二个问题是货型不匹配。零担小车和大挂车需要的月台不一样生鲜冷链和普通干货也不能共用一个月台简单的先到先得会导致月台类型错配前面一台车占着不合适的位置后面合适类型的车只能干等。第三个问题是高峰期完全失控。六台车同时到站六个月台刚好够用但其中两台是超过三个小时的超长装卸另外四台半小时就能完事先到先得把长作业车排前面整个站点吞吐量直接被拖垮。所以排队列表必须支持排序策略、优先级调整和月台类型匹配而不是简单的FIFO队列。这也是SLDS设计时最核心的一条原则。1.3 SLDS里这两个模块的定位与边界在我们的系统划分里排队列表模块负责车到站之后到上个月台之前这一段月台管理模块负责月台资源本身的状态维护和分配。两者之间的边界是月台分配指令——排队列表产出待分配车辆月台管理负责找到合适的月台并锁定锁定成功后车辆状态从排队中变成已分配开始驶向月台。这个边界划分有几个好处。第一是职责清晰排队模块不需要关心月台内部作业月台模块不需要关心队列顺序。第二是便于扩展如果某个站点特殊可以单独配置调度策略不影响其他站点。第三是故障隔离月台模块出问题时排队列表还能继续收车车辆可以先进入队列等月台恢复。2. 排队列表的数据模型与状态机设计2.1 排队表的核心字段设计数据模型是排队列表的地基。我见过不少项目把排队信息直接塞在订单表里加几个状态字段就算完事后面做统计、做优先级调整、做超时监控的时候全都傻眼。正确的做法是独立建一张排队表字段至少要覆盖车辆信息、队列状态、调度信息和时间戳四类。下面是我们实际用的表结构做了脱敏简化但核心字段都在。CREATE TABLE dock_queue ( id bigint(20) NOT NULL AUTO_INCREMENT, queue_no varchar(32) NOT NULL COMMENT 排队单号格式QD站点编码日期流水号, vehicle_no varchar(32) NOT NULL COMMENT 车牌号一个排队单只能绑定一台车, driver_name varchar(64) DEFAULT NULL COMMENT 司机姓名入场登记时录入, driver_phone varchar(20) DEFAULT NULL COMMENT 司机电话用于呼叫/通知, appointment_id bigint(20) DEFAULT NULL COMMENT 关联预约单ID空表示未预约直接到场, appointment_window varchar(32) DEFAULT NULL COMMENT 预约时段如 2025-06-01 08:00-10:00, arrive_time datetime DEFAULT NULL COMMENT 实际到站登记时间, queue_status tinyint(4) NOT NULL COMMENT 状态1排队中 2待分配 3已分配 4装卸中 5已完成 6已取消, queue_score int(11) DEFAULT 0 COMMENT 综合排序分越小越靠前, priority_type tinyint(4) DEFAULT 0 COMMENT 优先级类型0普通 1生鲜 2加急 3逆向优先, dock_id bigint(20) DEFAULT NULL COMMENT 分配的月台ID未分配为空, dock_start_time datetime DEFAULT NULL COMMENT 上个月台时间, dock_end_time datetime DEFAULT NULL COMMENT 作业完成时间, wait_duration_min int(11) DEFAULT 0 COMMENT 累计等待分钟数用于超时监控, cancel_reason varchar(255) DEFAULT NULL COMMENT 取消原因, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_status_score (queue_status, queue_score), KEY idx_vehicle (vehicle_no), KEY idx_appointment (appointment_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物流站点排队列表;这里有两个字段我特别提一下。一个是queue_score这是排序的核心但不是简单地等于到达时间。我建议在写入排队记录时就算好初始分后续优先级调整直接改分值而不是改时间字段。这样排序SQL就是简单的ORDER BY queue_score ASC, arrive_time ASC索引也能正常命中避免高峰期全表扫描。另一个是wait_duration_min这个字段一定要有。它不只是给报表看更重要的是驱动超时报警。我们当时设了一个阈值比如普通车辆等待超过90分钟自动触发调度提醒冷链车辆超过45分钟就报警这个字段配合定时任务轮询比每次现算时间差要省事得多。2.2 排队状态机从预约到离场一共几跳状态机设计是整个模块最关键的环节如果状态定义不清晰后面所有联调都会出问题。我们最终确定了六个核心状态外加三个边界状态处理逻辑。状态编码含义进入条件离开条件排队中1已入队等待分配月台车辆到站登记成功分配月台成功 / 用户取消待分配2排队靠前进入分配候选排队轮次触发或超时触发月台锁定成功 / 重新入队已分配3已锁定月台车辆驶向月台分配算法选中并锁定月台车辆到达月台并开始作业装卸中4月台作业进行中司机或系统确认开始作业作业完成确认已完成5作业完成等待离场作业完成确认离场放行已取消6排队或调度终止司机取消/超时未到场/系统取消无这里重点说待分配这个状态。很多新手设计时会把排队中和待分配合并成一个状态觉得都是在等。实际运行中你会发现中间必须加一层。原因有两个。第一分配月台是一个异步操作可能涉及到月台锁、事务提交、推送司机App这个操作不能卡在排队入队的同步逻辑里所以需要一个独立状态表示我已经进入分配流程了。第二从待分配状态可以很方便地做重新入队操作比如分配算法发现所有月台都不匹配或者月台锁定超时就把记录从待分配退回排队中重新排到队尾或者按规则插入合适位置。如果没有这个状态退回逻辑就是直接改状态字段语义上非常含糊。状态流转里有一个边界情况必须提前定义车辆到了已分配状态但迟迟没有开始作业怎么办我们规定了一个超时时间默认15分钟。如果15分钟内车辆没有到达月台并确认开始作业系统自动释放月台排队记录从已分配回退到排队中并标记一次违约违约次数会影响后续排队优先级。这个机制上线后确实减少了不少司机在园区里找不到月台的浪费情况。2.3 优先级与插队策略不能一刀切排队排序如果只看先来后到高峰期站点吞吐量一定上不去。但优先级设计也不能太复杂规则越多越难维护司机的理解成本也越高。我们最终用了三档优先级叠加一个紧急插队通道。默认情况下所有车辆按arrive_time排序这就是基础顺序。在此基础上冷链生鲜车辆、加急订单车辆可以提升优先级在排序分上直接减去一个固定权重。比如普通车初始分是1000加上到达时间戳的偏移量生鲜车分值是普通车的二分之一这样生鲜车基本能排到同批次普通车的前面。紧急插队通道单独处理不走排序分值而是提供一个强制置顶操作。这个操作只能由站点调度员在后台手动发起并且必须填写原因比如某台车延误导致下一班干线发车时间紧迫需要优先装卸。强制置顶会将该排队记录的queue_score设置为当前队列最小值减1同时推送通知给被挤后一位的司机说明插队原因。这个逻辑很朴素但效果好因为它把插队变成了一种有痕的、可审计的操作而不是调度员私下调整顺序。还有一个容易被忽略的细节同一台车多次进站。我们约定一台车同一时刻只能有一条有效的排队记录入场登记时如果发现该车牌已经存在状态为1到4的记录直接提示该车辆已在排队或作业中不允许重复入队。否则高峰期会出现同一台车占两个排队位的情况等系统发现异常时月台已经被白白占掉了。3. 月台分配与调度策略的实现细节3.1 月台资源模型先定义清楚月台是什么月台管理模块的第一步不是写分配算法而是把月台资源建模建对。我们一开始把月台简单定义成一个有状态的库位后来发现根本不够用因为同一个物理月台在不同时段、面向不同货型时能力是不一样的。最终的模型里月台实体上有四类关键属性。第一是基础属性包括月台编码、所属站点、物理位置、门号。第二是能力属性包括月台类型卸货台、装货台、双向台、支持的车辆类型大挂、厢式、冷链车、支持的货型干货、冷链、大件、危险品、最大停靠时长。第三是状态属性包括空闲、占用、预约、维护、禁用五种状态。第四是时间属性包括当前作业的开始时间和预计结束时间、下一段预约占用时间。这个模型里最值得重视的是能力属性和时间属性。很多项目月台管理做不到精细化调度就是因为月台没有能力标签系统只知道月台空不空不知道这个月台能不能接这票货、还能接多久。我们加上了能力匹配和预计结束时间之后分配算法才有了真正的决策依据。月台表结构简化为这样CREATE TABLE dock_info ( id bigint(20) NOT NULL AUTO_INCREMENT, dock_code varchar(32) NOT NULL COMMENT 月台编码, station_id bigint(20) NOT NULL COMMENT 所属站点ID, dock_type tinyint(4) NOT NULL COMMENT 1卸货 2装货 3双向, dock_status tinyint(4) NOT NULL COMMENT 0禁用 1空闲 2占用 3预约 4维护, support_vehicle_types varchar(255) DEFAULT NULL COMMENT 支持的车辆类型逗号分隔, support_cargo_types varchar(255) DEFAULT NULL COMMENT 支持的货型逗号分隔, max_duration_min int(11) DEFAULT 480 COMMENT 最大单次作业时长分钟, current_queue_id bigint(20) DEFAULT NULL COMMENT 当前作业的排队单ID, current_start_time datetime DEFAULT NULL COMMENT 当前作业开始时间, expected_end_time datetime DEFAULT NULL COMMENT 预计作业结束时间, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_station_status (station_id, dock_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT月台信息表;这里有一个设计取舍值得说为什么把current_queue_id放在月台表里而不是反过来在排队表里单靠dock_id关联我们当时确实纠结过最后决定两边都存。排队表存dock_id是为了查询方便月台表存current_queue_id是为了保证分配时的唯一性约束。更新时在同一个数据库事务里同时写两张表用月台表的current_queue_id作为唯一的作业占用标记。这样要查询这个月台现在在干什么直接查月台表要查询这台车现在在哪里直接查排队表都不需要额外的关联查询高峰期性能压力会小很多。3.2 月台分配算法从先到先得到最短完工时间优先月台分配是排队列表和月台管理之间的桥梁。我们的分配算法不是一次性给所有排队车辆分配月台而是采用事件驱动定时扫描双触发机制。事件驱动指的是排队列表里有新车辆进入待分配状态、或者月台状态从占用变为空闲时立即触发一次分配。定时扫描指的是每30秒扫一次排队中但等待时间超过阈值的车辆强制把它们推进分配流程防止事件丢失导致车辆被卡住。分配算法的核心逻辑用伪代码表达是这样的function allocateDock(queueItem, idleDocks): // 第一步能力过滤 candidates filter(idleDocks, dock.dock_type matches queueItem.loadType and queueItem.vehicleType in dock.support_vehicle_types and queueItem.cargoType in dock.support_cargo_types) if candidates is empty: return null // 第二步排序选择 // 策略1优先选择预计最早空闲的月台最短作业剩余时间 // 策略2优先选择作业次数最少、空闲最久的月台均衡磨损 // 默认走策略1如果两个策略选出的月台相同则直接使用 bestDock min(candidates, key (dock.expected_end_time if dock.status 占用 else dock.last_free_time)) return bestDock策略1的本质是最短完工时间优先让整个站点的月台吞吐量最大化。举个例子两个月台都空闲A月台刚刚完成了一台超长装卸车场地里货物还没清完预计整理需要20分钟B月台刚清完随时可用。此时新到一台车分配算法应该选B而不是机械地选月台编号靠前的A。这个策略上线后高峰期月台利用率提升了大约15%到20%。策略2是一个均衡策略防止某些月台因为位置好被反复使用、设备磨损严重而靠里的月台长期闲置。两种策略的权重可以在站点级别配置有的站点吞吐优先有的站点设备保养优先灵活度比较高。分配算法还有一个约束必须处理司机和月台的物理距离。有的站点月台分布在多个库房车辆在A区排队分配算法却给它分配了B区最远的月台司机需要绕很大一圈。我们当时在算法里加了一个距离代价参数月台与排队车辆当前所在区域的匹配优先。如果A区有可用月台即使B区月台预计更早空闲也优先分配A区除非等待时间超过10分钟才跨区分配。这个参数一开始我们没加上线后被司机投诉了好几次才知道要加。3.3 排队列表和月台的联动谁驱动谁这两块模块联动时最容易踩的坑是循环依赖。排队列表等月台分配月台分配等排队列表推荐两边互相等着结果整个流程卡死。我们最终确定的驱动关系是单向的排队列表是主动方月台管理是被动方。排队模块负责决定哪些车辆进入待分配池月台模块只负责在待分配池里选车并锁定月台。月台模块永远不会主动去排队列表里拉车它只响应分配请求。为什么这样定因为月台模块如果主动拉车就会出现月台空闲但不去拉、要等定时任务的情况导致闲置窗口被拉长。而排队模块做主动方可以在车辆入队、取消、超时等关键节点立刻推动状态流转实时性更好。联动还有一个细节是月台释放后立即触发补位。一辆车作业完成释放月台系统马上从待分配池里按排序分取下一台车将月台状态改为预约不是直接占用同时通知司机15分钟内到月台。预约状态是防止月台在被占用前的空窗期被其他流程抢走。如果15分钟内车辆未到达预约自动释放车辆重新排队并记违约。这个预约缓冲机制极大降低了月台空转率我强烈建议保留。4. 全流程实操从预约到离场的完整链路4.1 预约与入场登记完整的车辆进站流程从预约开始。司机在TMS运输管理系统里预约到站时段系统按站点能力生成预约单。预约不是强制的但预约车辆在排队时有时段加成可以减少等待时间。我们实测下来预约率从最初的30%提升到70%之后高峰期车辆平均等待时长下降了约四分之一。车辆实际到站后司机在站门口的司机App或自助终端上登记录入车牌号并确认预约单如果有。系统首先校验车辆是否有有效预约、是否有未完成的排队记录、是否在黑名单中。校验通过后生成排队单状态置为排队中queue_score按规则计算并写入。这里有一个实操细节为了防止司机提前到达但没有预约导致排队不公平我们规定未预约车辆到站后先进入等待池等待5分钟如果5分钟内有预约取消或者名额空出则正常入队否则按实际到达时间补入队。这个缓冲机制避免了一个大问题——大量未预约车辆涌入时把预约车辆的时段全部挤占。4.2 排队与月台分配触发车辆入队后系统主流程进入排队逻辑。定时任务每30秒扫描一次排队中且排序靠前的车辆把前N台N一般为空闲月台数的1.5倍推入待分配状态。为什么要1.5倍因为总有人会临时取消、超时不到多备一些候选车辆给分配算法选择可以减少月台空闲概率。待分配车辆由分配算法进行月台匹配。匹配成功的排队记录状态改为已分配月台状态改为预约同时给司机推送月台编号和预计上台上时间。同步写入一条调度日志记录分配时间、分配算法选择的策略、候选月台数量、最终选中的月台和原因分数方便事后审计。分配失败的情况也要处理如果候选月台列表为空记录会从待分配退回排队中并且wait_duration_min继续累加。这里要注意退回操作不能频繁触发否则车辆会在两个状态之间来回抖动。我们的做法是每台车最多连续尝试两次分配第二次还是失败就进入人工介入清单由站点调度员手动处理。4.3 装卸作业与离场确认车辆到达月台后司机在App上点击开始作业系统将排队记录状态从已分配改为装卸中月台状态从预约改为占用并记录dock_start_time。这个确认动作一定要由司机发起而不是系统自动判断否则车辆还没到月台就开始计时提前触发超时释放就麻烦了。作业过程中系统持续跟踪预计结束时间。这里我们接入了WMS的作业进度数据每月台维护一个预计剩余分钟数由人工在PDA上更新或者根据实际件数、装卸速率自动估算。作业完成时仓库人员在PDA确认作业完成系统自动将月台状态改为空闲排队记录状态改为已完成记录dock_end_time计算实际作业时长随后车辆通过离场闸口自动放行。离场闸口是最后一道关卡。系统校验该车辆排队记录状态必须为已完成且没有关联的待处理异常比如货物破损待确认校验通过后才抬杆放行。这一步可以防止司机作业没完成就离场后续追责找不到人。5. 上线后踩过的坑与排查实录5.1 排队假死状态卡在排队中不动上线第一周就遇到一个问题部分车辆排队记录长期停留在排队中但队列前面的车早就清空了按说早该轮到它们。查日志发现这些车辆在入队时正好赶上一次月台分配事务回滚入队的数据库操作成功了但触发分配的事件没有发出来变成有记录无事件状态。排查思路是比对dock_queue表里queue_status1的记录数量和对应时间窗口内的分配事件日志。如果发现排队记录数量大于分配事件触发次数基本可以确认有事件丢失。解决方法是双管齐下一是把入队成功改成事务完成后立即发送事件确保事件不丢二是保留30秒定时扫描兜底扫描时把所有queue_status1且wait_duration_min超过阈值、且队列前序没有阻塞记录的车辆强制推入待分配状态。定时扫描是兜底防线即使事件全丢最晚30秒内也能恢复流程。这套机制上线后没有再出现假死。5.2 月台闲置但排队很长分配策略失效还有一次现象很诡异系统显示排队等待的车辆很多但看月台状态有两个月台明明空闲却一直没分配出去。查了分配日志才发现那两个空闲月台在能力属性里被标记为只支持大挂车而排队前列的车辆都是厢式车算法匹配不上于是月台空着车辆也进不来。这个问题的根源是月台能力标签配置太粗。站点实际作业中很多月台是通用的厢式车也能停大挂车月台只是效率略低。我们后来在能力匹配里加了一个兼容模式参数支持能力完全匹配优先如果排在前面的候选车辆能力不匹配则允许降级匹配到兼容月台但要在分配日志里标记兼容分配并扣一点排序分作为补偿。这个逻辑上线后月台闲置率明显下降。5.3 并发场景下的数据不一致排队表和月台表在两个地方同时更新并发高峰时出现过月台被两台车同时分配的情况。具体场景是两台车同时进入待分配状态分配算法同时扫描到同一个月台空闲都执行了锁定结果月台的current_queue_id被后写入的覆盖前一台车也以为自己分配成功了。解决方式是在分配月台时使用数据库的乐观锁。更新月台表时带上条件WHERE dock_status 1,如果更新影响行数为0说明月台已经被占用本次分配失败重新选择其他月台。同时把排队记录状态更新和月台状态更新放在同一个事务里确保要么都成功要么都失败。UPDATE dock_info SET dock_status 3, current_queue_id #{queueId} WHERE id #{dockId} AND dock_status 1;这条SQL是关键。如果影响行数为0说明有其他事务抢先了直接放弃当前选中的月台重新跑分配算法。这里不要用SELECT ... FOR UPDATE做行锁因为锁等待在高并发下会导致大量线程堆积反而拖垮数据库。5.4 常见问题速查表问题现象可能原因排查方式解决方案车辆排队但长时间不分配分配事件丢失对比入队记录与事件日志定时扫描兜底 事件补偿月台空闲但排队很长能力标签过严检查月台能力属性与排队车辆类型增加兼容分配模式月台被重复分配并发更新无锁查看月台分配日志乐观锁条件更新司机收不到月台通知推送配置异常检查司机App登录态增加短信兜底通知排队顺序不公平排序分值算法不合理检查排序分计算日志统一排序分计算规则车辆到了月台但不开始作业司机未确认查看排队状态停留时长设置自动提醒 超时释放同一车牌重复排队入队校验缺失查询该车牌有效排队记录入队前唯一性校验6. 月台管理后续还能怎么扩展这套体系运行稳定之后我们已经在做两个方向的扩展这里分享给打算继续深入的同学。第一个是按时间段做月台预约能力。目前月台分配依赖车辆到站后触发现场调度下一步可以支持货主或者承运商提前预约月台时段系统直接在预约阶段锁定时段和月台车辆到站后无需现场排队直接上台。这就要求月台表增加一个时间片表结构并且把分配算法从找当前空闲月台升级为找未来某时段空闲月台整体调度复杂度会上一个台阶但体验提升也是明显的。第二个是排队积压预测。现在我们积累了足够的历史排队数据可以用简单的统计模型预测未来1到2小时内的到达车辆数和排队积压程度提前提醒站点增开月台或者引导车辆错峰到站。实际做下来哪怕只是基于历史均值加一个波动系数做的粗略预测也能帮助站点调度员提前半个多小时做好准备这个提前量在高峰期非常宝贵。做这块内容的过程中我个人体会最深的一点是排队和月台调度本质上不是一个算法问题而是一个流程问题。算法再好如果车辆到站登记不规范、司机确认动作不及时、月台释放流程不闭环整个系统一样跑不起来。所以做物流系统一定要把线上流程和线下实际操作节奏对齐多去站点现场看两趟比在办公室憋一个月设计文档都有用。最后再分享一个小技巧所有调度相关的规则变更都先在测试站点灰度运行一周同时保留人工介入的开关。规则这东西放到真实场景里跑几天就会暴露一堆你没想到的边界情况千万别一上来就全量铺开。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门