398 lines
22 KiB
Markdown
398 lines
22 KiB
Markdown
# P3 模块自治架构设计(终态目标)
|
||||
|
|
|
|||
|
|
## 文档信息
|
|||
|
|
|
|||
|
|
- **版本**:V1.0(设计稿)
|
|||
|
|
- **日期**:2026-08-14
|
|||
|
|
- **状态**:设计评审中,未实施
|
|||
|
|
- **前提**:新项目,数据可重置,不兼容旧架构;未来支持多机器人等共享资源
|
|||
|
|
- **背景**:V0.6 全配置化流程引擎(`internal/engine/`)在生产暴露"全局耦合 + 机会主义触发"的结构性缺陷,连续发生多次全线死锁。本文档定义终态架构,历史缺陷清单见[第 9 章](#9-历史缺陷清单与架构对策)(每个缺陷对应一条架构设计约束)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
|
|||
|
|
1. [架构总览](#1-架构总览)
|
|||
|
|
2. [核心设计原则](#2-核心设计原则)
|
|||
|
|
3. [工件档案(Job as Data)](#3-工件档案job-as-data)
|
|||
|
|
4. [设备模块](#4-设备模块)
|
|||
|
|
5. [搬运需求协议与预约制](#5-搬运需求协议与预约制)
|
|||
|
|
6. [共享资源服务(多机器人仲裁)](#6-共享资源服务多机器人仲裁)
|
|||
|
|
7. [触发与轮询双机制](#7-触发与轮询双机制)
|
|||
|
|
8. [数据模型](#8-数据模型)
|
|||
|
|
9. [历史缺陷清单与架构对策](#9-历史缺陷清单与架构对策)
|
|||
|
|
10. [实施路径](#10-实施路径)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 架构总览
|
|||
|
|
|
|||
|
|
三层架构:**工件档案层(数据)→ 设备模块层(状态机)→ 共享资源服务层(仲裁)**。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 工件档案层(Job as Data) │
|
|||
|
|
│ 身份 / 位置 / 进度 / 质检结果 —— 只记录,不驱动,随工件流转被读写 │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
▲ 工序完成事件 │ 工序就绪请求(按配方查下一工序)
|
|||
|
|
│ ▼
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 设备模块层(每设备 = 一个独立状态机,各自 触发+轮询 双机制) │
|
|||
|
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|||
|
|
│ │ 磨床模块 │ │ 清洗模块 │ │ 全检模块 │ │ 缓存模块 │ │ 接驳模块 │ │
|
|||
|
|
│ │ 加工/换料 │ │ 吹洗 │ │ 批次检测 │ │ 缓冲/回流│ │ AGV交互 │ │
|
|||
|
|
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
│ 搬运需求(装载→运输→卸载原子任务,目的地先预约)
|
|||
|
|
▼
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 共享资源服务层(多机器人仲裁) │
|
|||
|
|
│ 任务队列 + 优先级 + 目的地预约 + 机器人池匹配(未来可扩展任意共享资源)│
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**核心转变**:工件(job)从"流程执行者"退化为"数据档案";"该发生什么"从"某个 job 恰好走到决策点"变为"设备模块状态机的转移条件 + 仲裁器的调度规则"。死锁在架构层面不可构造(见[第 2 章](#2-核心设计原则))。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 核心设计原则
|
|||
|
|
|
|||
|
|
每条原则对应一个历史缺陷(详见[第 9 章](#9-历史缺陷清单与架构对策)):
|
|||
|
|
|
|||
|
|
| # | 原则 | 消灭的缺陷 |
|
|||
|
|
|---|------|-----------|
|
|||
|
|
| P1 | **预约制**:搬运任务派发前必须预约目的地槽位;预约失败 → 任务不启动(工件留原设备或改道缓存台) | 气吹持料死锁、换料持锁死锁 |
|
|||
|
|
| P2 | **资源锁生命周期 = 任务执行期**:robot/temp_slot 锁只存在于"装载→运输→卸载"原子窗口,不存在于任何模块状态 | 换料后持锁进等待全线死锁 |
|
|||
|
|
| P3 | **系统不变量 = 模块状态机一等转移条件**:满 2 件发 M2102、done 滞留强制取料等,是模块内部逻辑,不依赖任何外部 job 走到决策点 | 全检台满件 M2102 无人发 |
|
|||
|
|
| P4 | **模块间只通过事件/数据通信**:不直接改对方状态、不共享锁;唯一共享的是资源服务(机器人) | 全局决策分支互相耦合、牵一发动全身 |
|
|||
|
|
| P5 | **双机制兜底**:事件触发 + 轮询自检,事件丢失由自检兜底 | 广播无等待者、信号先到竞态 |
|
|||
|
|
| P6 | **信号语义由模块协议定义**:工件类型/位置号是搬运协议的数据字段,不是可漏写的配置 | CWT 漏发、位置号地址复用混乱 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 工件档案(Job as Data)
|
|||
|
|
|
|||
|
|
### 3.1 现状 vs P3
|
|||
|
|
|
|||
|
|
| | 现状(V0.6) | P3 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| job 的角色 | 流程执行者:acquire 资源、发信号、决策跳转 | 数据档案:只记录,不驱动 |
|
|||
|
|
| 步骤状态机 | `job_step` 快照表 + ACQUIRE→EXECUTE→WAIT→COMPLETE | 无步骤表,工序进度 = 追加事件 |
|
|||
|
|
| 推进动力 | 引擎全局调度(GetActiveJobsOrdered 遍历) | 设备模块状态机 + 事件 |
|
|||
|
|
| 工序完成 | job 自己推进 next_step | 模块写事件 → 档案更新 → 按配方请求下一工序 |
|
|||
|
|
|
|||
|
|
### 3.2 表结构
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 工件档案:身份 + 位置 + 进度(无步骤状态机)
|
|||
|
|
CREATE TABLE job (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
work_order_id BIGINT NOT NULL, -- 工单
|
|||
|
|
product_type_id BIGINT NOT NULL, -- 产品类型(CWT 数据源)
|
|||
|
|
recipe_id BIGINT NOT NULL, -- 工艺路径(配方 = 数据)
|
|||
|
|
step_index INT NOT NULL DEFAULT 0, -- 已推进到的工序序号(配方 steps 数组下标)
|
|||
|
|
loc_equipment TEXT NOT NULL DEFAULT '', -- 当前位置:设备类型(GRINDER/WASH/FULL_CHECK/...)
|
|||
|
|
loc_slot_no INT NOT NULL DEFAULT 0, -- 当前位置:槽位号
|
|||
|
|
inspect_result TEXT, -- 质检结果
|
|||
|
|
status TEXT NOT NULL DEFAULT 'CREATED', -- CREATED/IN_TRANSIT/AT_EQUIPMENT/COMPLETED/SCRAPPED
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 工序完成事件(append-only,进度真相源,可重放恢复)
|
|||
|
|
CREATE TABLE job_event (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
job_id BIGINT NOT NULL,
|
|||
|
|
event_type TEXT NOT NULL, -- STEP_DONE / TRANSPORT_DONE / INSPECT_DONE / SCRAPPED ...
|
|||
|
|
payload JSONB NOT NULL, -- {equipment:"GRINDER", slot:1, step:"GRIND", ...}
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- 配方 = 工艺路径数据(不再有决策分支/资源/信号子表)
|
|||
|
|
CREATE TABLE recipe (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
steps JSONB NOT NULL -- [{"step":"GRIND","equip":"GRINDER","params":{...}}, ...]
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 推进机制(事件驱动,幂等可重放)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
设备模块完成工序
|
|||
|
|
→ 写 job_event(STEP_DONE) + 更新 job.step_index / loc
|
|||
|
|
→ 按 recipe.steps[step_index+1] 查下一工序 → 请求目标设备模块(投递其事件队列)
|
|||
|
|
→ 目标模块评估(有空位?可接收?)
|
|||
|
|
→ 有 → 产生搬运需求(投入仲裁队列)
|
|||
|
|
→ 无 → 请求挂起(工件留在原设备,不占任何锁;模块自检在有空位时重新评估)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **幂等**:job_event 是进度真相源,重启后从事件表重建各模块状态,无需步骤快照
|
|||
|
|
- **工序间解耦**:每个模块只关心"我这里有活干吗",不关心全局下一个是谁
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 设备模块
|
|||
|
|
|
|||
|
|
### 4.1 通用模块模式
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 所有设备模块实现统一接口
|
|||
|
|
type EquipmentModule interface {
|
|||
|
|
OnEvent(ctx, evt ModuleEvent) error // 事件驱动:信号/搬运完成/工序请求
|
|||
|
|
SelfCheck(ctx) error // 轮询兜底:超时/滞留/满仓检测
|
|||
|
|
ReserveSlot(ctx, jobID, kind) (SlotRef, error) // 目的地预约(供仲裁器调用,P1 预约制)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
模块内部状态机通用骨架:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
IDLE → OCCUPIED(工序执行中)→ DONE(完成待搬运)→ IDLE
|
|||
|
|
↑ │
|
|||
|
|
└──────── 搬运完成 ──────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **状态只存在于模块内存 + 本模块的表**,其他模块不可见不可改(P4)
|
|||
|
|
- 每个模块自带:事件队列(串行处理,天然防竞态)+ 自检 tick(P5)
|
|||
|
|
|
|||
|
|
### 4.2 磨床模块
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
状态:IDLE / 加工中(GRINDING) / 完成滞留(DONE) / 换料中(EXCHANGING)
|
|||
|
|
事件:放料完成(M2049) → GRINDING;加工完成(M2057) → DONE → 产生搬运需求
|
|||
|
|
后继毛坯到达(搬运任务到达)→ EXCHANGE 任务(一次取成品+放毛坯)
|
|||
|
|
自检:DONE 滞留超时 → 强制产生取料需求(磨床空出,不无限等后继)
|
|||
|
|
规则:加工完成不"等后继换料"挂起——超时即取料收尾,磨床优先空出(对应 V0.6 handleHolding 逻辑内聚)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 清洗模块(气吹机构)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
状态:IDLE / 吹洗中(WASHING) / 完成待搬运(DONE)
|
|||
|
|
事件:放料完成(M2139?) → WASHING;吹洗完成 → DONE
|
|||
|
|
规则(修正持料模型,P1/P2):
|
|||
|
|
- 吹洗完成瞬间 → 先预约目的地(全检台空位 / 缓存台空位 / 全检 done 槽换料位)
|
|||
|
|
- 预约成功 → 派发搬运任务,机器人从气吹机构直接 carry 到目的地(物理行为与现状一致)
|
|||
|
|
- 预约失败 → 工件留在气吹机构(不交给 robot),模块自检持续重试预约;
|
|||
|
|
若全检台满 + 缓存台满 → 系统满仓告警(物理无解,人工介入)
|
|||
|
|
自检:WASHING 超时 → 告警;DONE 预约超时 → 告警 + 重试
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**对比现状(缺陷 1)**:现状是"机器人夹着工件(持锁)再找地方放",全检台满了 robot 就死等;P3 是"先确认地方再拿 robot",robot 永远不会持料等待。
|
|||
|
|
|
|||
|
|
### 4.4 全检模块(批次检测内聚)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
槽位对状态:(S1,S2)
|
|||
|
|
EMPTY/EMPTY → 放料 → OCCUPIED/EMPTY → 放料 → OCCUPIED/OCCUPIED
|
|||
|
|
→ 【满 2 件自动发 M2102(模块一等转移条件,P3 原则)】→ INSPECTING(等 M2098)
|
|||
|
|
→ DONE/DONE → 产生取料搬运需求(每 done 槽一件)/ 换料任务(后继待检件到达时)
|
|||
|
|
→ EMPTY/EMPTY
|
|||
|
|
事件:放料完成(M2109)、检测完成(M2098 广播)、搬运完成
|
|||
|
|
自检:INSPECTING 超时 → 告警(检测卡死);DONE 滞留超时 → 强制取料需求
|
|||
|
|
规则:
|
|||
|
|
- 换料:有 done 槽 + 待检件到达 → EXCHANGE 任务(一次取 done + 放待检,对应 OP40_EXCHANGE)
|
|||
|
|
- 批次检测逻辑(满 2 件发 M2102、广播唤醒、收尾批次)全部内聚本模块,不依赖任何 job
|
|||
|
|
- M2102 防重复:状态机只在 OCCUPIED/OCCUPIED → INSPECTING 转移时发一次
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.5 缓存模块(缓冲队列)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
状态:槽位 空/占(N 个槽)
|
|||
|
|
角色:目的地不可用时的改道目标(P1);回流源(全检台有空位时产生回流搬运需求)
|
|||
|
|
事件:放料完成、取料完成
|
|||
|
|
自检:占用超时(无回流需求产生)→ 告警
|
|||
|
|
规则:全检台有空位 + 缓存台有件 → 自动产生回流搬运需求(对应 V0.6 缓存回流内聚)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.6 接驳台模块(AGV 交互)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
角色:槽位统一分配器(消灭缺陷 8:"job 未绑定槽位回退任意槽位")
|
|||
|
|
放料(AGV 来料)/ 取料(成品下料回 AGV)
|
|||
|
|
事件:AGV 到位、放料完成(M2019)、取料完成(M2009)
|
|||
|
|
规则:槽位分配 = 模块内部数据,不散落在 job 行
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 搬运需求协议与预约制
|
|||
|
|
|
|||
|
|
### 5.1 需求结构(模块间唯一接口)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type TransportRequest struct {
|
|||
|
|
ID string
|
|||
|
|
JobID int
|
|||
|
|
From SlotRef // 源:设备类型 + 槽位号
|
|||
|
|
To SlotRef // 目标:预约时确定
|
|||
|
|
Kind Kind // MOVE(普通搬运)/ EXCHANGE(换料:取+放一次)/ REDIRECT(改道)
|
|||
|
|
Priority int // EXCHANGE > 紧急取料 > 普通搬运
|
|||
|
|
State State // QUEUED → RESERVED → EXECUTING → DONE
|
|||
|
|
// ↘ REDIRECTED(改道缓存台)→ DONE
|
|||
|
|
// ↘ CANCELLED(源槽位状态变化)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 任务生命周期(预约制核心,P1)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 源模块产生需求 → QUEUED(不占任何锁、不占 robot)
|
|||
|
|
2. 仲裁器按优先级选任务 → 调目标模块 ReserveSlot()(事务内锁定目标槽位)
|
|||
|
|
├─ 成功 → RESERVED → 派发 robot → EXECUTING
|
|||
|
|
│ robot 执行"装载→运输→卸载"原子窗口(P2:锁只存在于此窗口)
|
|||
|
|
│ → 完成事件 → DONE → 通知源/目标模块 + 更新 job 档案
|
|||
|
|
├─ 失败(目标满)→ 改道:REDIRECTED(目标=缓存台,若有空位)
|
|||
|
|
│ └─ 缓存台也满 → 任务挂起(源模块自检重试预约,P5)
|
|||
|
|
└─ 源槽位状态变化(工件被其他任务取走)→ CANCELLED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**防死锁不变量**:
|
|||
|
|
- robot 派发时目标已预约锁定,物理上不可能出现"到了没地方放"
|
|||
|
|
- 源模块产生需求不持锁,任务挂起不占 robot
|
|||
|
|
- 唯一可能的僵局 = 系统满仓(全检满 + 缓存满 + 无取料动作)→ 告警,物理无解只能人工
|
|||
|
|
|
|||
|
|
### 5.3 多段搬运优化(对应现状"持料 carry 连续多段")
|
|||
|
|
|
|||
|
|
清洗 → 全检 → 下料一次连续搬运(效率优化):**预约链**——连续段的目标全部预约成功才允许启动;任一段预约失败 → 中断在安全点(工件落缓存台),不持料等待。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 共享资源服务(多机器人仲裁)
|
|||
|
|
|
|||
|
|
### 6.1 机器人池
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE robot (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
zone TEXT, -- 工作区域(就近调度用)
|
|||
|
|
status TEXT NOT NULL DEFAULT 'IDLE', -- IDLE/BUSY/MAINTENANCE
|
|||
|
|
current_task_id TEXT,
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 仲裁器(Robot Dispatcher)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
每 tick / 任务事件:
|
|||
|
|
1. 从队列取最高优先级任务(同优先级 FIFO)
|
|||
|
|
2. 目标模块 ReserveSlot()(失败 → 改道/挂起,见 5.2)
|
|||
|
|
3. 匹配空闲机器人:zone 就近优先 → 轮询均衡
|
|||
|
|
4. 派发 → robot.BUSY + task.EXECUTING → 完成事件 → robot.IDLE
|
|||
|
|
5. 自检:任务 EXECUTING 超时 → 告警(机器人卡死/物理故障)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.3 多机器人并行安全
|
|||
|
|
|
|||
|
|
- **目的地槽位预约串行化**:同一槽位同一时刻只被一个任务预约(事务锁),两个 robot 不会抢同一目标
|
|||
|
|
- **源槽位状态锁定**:任务 RESERVED 后源槽位标记,防止两个任务重复取同一工件
|
|||
|
|
- robot 之间无共享状态,天然并行
|
|||
|
|
|
|||
|
|
### 6.4 扩展:任意共享资源
|
|||
|
|
|
|||
|
|
机器人只是共享资源的一种。未来 AGV、夹具、检测仪等同构接入:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type SharedResource interface {
|
|||
|
|
Acquire(ctx, taskID) (bool, error) // 预约/占用
|
|||
|
|
Release(ctx, taskID) error
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
调度规则统一:任务 → 资源预约(成功才执行)→ 执行 → 释放。预约制在所有共享资源上复用,死锁不可构造。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 触发与轮询双机制
|
|||
|
|
|
|||
|
|
每个模块独立运行两条线(P5):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
触发线(即时):PLC 信号上升沿 → 模块事件队列(串行)→ 状态机转移
|
|||
|
|
轮询线(兜底):模块自检 tick(如 30s)→ 超时/滞留/满仓/任务卡死检测 → 自动动作或告警
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 自检项 | 检测 | 动作 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 工序超时 | 模块状态停留超阈值 | 告警 |
|
|||
|
|
| 滞留 | DONE 槽位超时无搬运需求 | 自动产生取料需求(磨床/全检台) |
|
|||
|
|
| 满仓 | 全检满 + 缓存满 + 无取料 | 告警(人工介入) |
|
|||
|
|
| 检测卡死 | INSPECTING 超时 | 告警(重发 M2102 或人工) |
|
|||
|
|
| 任务卡死 | EXECUTING 超时 | 告警(机器人故障) |
|
|||
|
|
| 预约失败循环 | 挂起任务超时 | 告警 |
|
|||
|
|
|
|||
|
|
事件丢了 → 自检兜底;自检慢了 → 事件即时。**不存在"信号没人等"的盲区**(对比缺陷 6/7:广播无等待者、信号先到竞态——事件队列 + 自检天然覆盖)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 数据模型
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
job 工件档案(身份/位置/进度,见 3.2)
|
|||
|
|
job_event 工序完成事件(append-only 真相源)
|
|||
|
|
recipe 工艺路径(steps JSONB)
|
|||
|
|
work_order 工单
|
|||
|
|
product_type 产品类型
|
|||
|
|
equipment / equipment_slot / equipment_type 设备与槽位(模块私有数据,但库表统一)
|
|||
|
|
robot 机器人池
|
|||
|
|
transport_task 搬运任务表(仲裁队列持久化,重启可恢复)
|
|||
|
|
module_state 模块状态快照(可选:重启恢复加速,非真相源)
|
|||
|
|
event_log / alarm 事件与报警(管理后台)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**重启恢复**:从 job_event + transport_task 重建全部模块状态(事件可重放),无步骤快照一致性难题。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 历史缺陷清单与架构对策
|
|||
|
|
|
|||
|
|
> 本节为 V0.6 及更早版本在生产中暴露的全部关键缺陷。**每条缺陷都对应第 2 章的一条设计原则**——P3 架构评审时逐条核对,确保不再重犯。
|
|||
|
|
|
|||
|
|
| # | 缺陷 | 现象 | 根因 | 架构对策(P3) |
|
|||
|
|
|---|------|------|------|----------------|
|
|||
|
|
| 1 | **气吹机构持料死锁** | 机器人夹持工件在气吹机构吹洗(无放料位置,M2139 完成 robot 不释放),carry 去下一步;下一步不可用(全检台满)→ robot 被持死,全线瘫痪(2026-08-14 job6 持 robot 卡 OP35_DECIDE 16 分钟) | 持料模型:"先拿 robot 锁,再找目的地";目的地满则死等 | **P1 预约制**:先预约目的地再派 robot;预约失败任务不启动。清洗模块 DONE 状态持续重试预约,robot 永不持料等待 |
|
|||
|
|
| 2 | **换料后持锁进等待死锁** | 换料完成后持前任成品 + robot 锁进入决策/等待 → 锁死全线(2026-08-13 线上 M2089 后全线卡死事故;配方注释明确"禁止持料进决策") | 资源锁生命周期 = job 步骤生命周期,锁随 job 进等待步骤 | **P2 锁生命周期 = 任务执行期**:模块状态机不持有全局锁;换料是一个原子 EXCHANGE 任务,完成后锁即释放 |
|
|||
|
|
| 3 | **全检台满 2 件 M2102 无人发** | 全检台 2 槽满待检件,但无任何 job 到批次决策点(换料路径被缓存回流分支拐走)→ 检测不开始 → 全线死锁(2026-08-14,16:12-16:22) | 系统不变量由 job 流程"机会主义"触发,路径一绕路就没人管 | **P3 模块一等转移条件**:满 2 件发 M2102 是全检模块状态机内部转移,与 job 流程完全无关 |
|
|||
|
|
| 4 | **决策回退无限空转** | 决策跳转 → acquire 失败 → 回退决策 → 条件未变 → 同一分支 → 循环(OP61_DECIDE↔OP50_EXCHANGE_TAKE,每事件 4 个 DB 事务 + 日志刷屏) | 决策-跳转-失败-回退机制无防循环保护 | 架构消灭:无全局决策步骤,无决策跳转,无回退 |
|
|||
|
|
| 5 | **GROUP BY 缺失调度崩溃** | successorCanExchange SQL 缺 GROUP BY → SQLSTATE 42803 → 引擎推进失败 → 全线卡死 | SQL 缺陷(依赖全局 job 关联查询) | 无此 SQL:模块间不查对方状态 |
|
|||
|
|
| 6 | **广播信号无等待者** | M2098 广播上升沿无任何 job 在等("缓存未匹配"×2)→ 检测结果无人消费,死锁前兆 | 广播依赖"恰好有 job 注册等待",时序竞态 | **P5 双机制**:模块状态机事件队列 + 自检兜底,不依赖瞬时注册 |
|
|||
|
|
| 7 | **Done 信号先到竞态** | 信号到达时步骤尚未进入 waiting_done → 信号丢失(pendingSignals 缓存 + 5s 时效校验是补丁) | 信号消费依赖步骤状态瞬时匹配 | 模块事件队列串行处理 + 状态机自检(P5) |
|
|||
|
|
| 8 | **接驳台槽位未绑定回退任意** | job 未绑定接驳台槽位时 acquire 回退"任意槽位"→ 位置语义错乱风险(日志 WARN 高频出现) | 槽位分配分散在 job 行内字段,无统一管理 | 接驳台模块统一分配槽位(P4 模块私有数据) |
|
|||
|
|
| 9 | **CWT 工件类型漏发** | 每组信号都应携带 M2022 工件类型,历史出现漏发(用户生产反馈) | 信号序列人为配置,工件类型是"可选的配置项" | 工件类型是 job 档案的必填字段,搬运协议强制携带(P6) |
|
|||
|
|
| 10 | **位置号地址复用混乱** | M2082(换料放位置)与 M2101(放料位置)共用 PLC 地址,语义靠配方 desc 注释区分 | 信号地址语义散落在配方描述 | 信号语义由模块协议定义,地址绑定在模块内(P6) |
|
|||
|
|
|
|||
|
|
**附加经验(非缺陷,但影响设计)**:
|
|||
|
|
- 持料模型是生产科室物理规则(气吹机构无放料位置),P3 保留物理行为,仅改软件时序(先预约后派发)
|
|||
|
|
- 磨床"断料才磨完即走、后继能取料则等换料交接":内聚为磨床模块自检规则(DONE 滞留超时强制取料)
|
|||
|
|
- recipe 表历史字段坑(无 is_del,用 is_active):新架构 recipe 表重新设计,无此问题
|
|||
|
|
- 全量测试会卡死生产:P3 模块化后单元测试按模块隔离,天然可并行
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 实施路径
|
|||
|
|
|
|||
|
|
前提:新项目数据可重置。两条路:
|
|||
|
|
|
|||
|
|
**路线 A(推荐):直接按 P3 落地**(新项目无历史包袱)
|
|||
|
|
1. 数据模型 + 事件系统(job_event / transport_task / 模块状态机骨架)
|
|||
|
|
2. 设备模块 × 5(磨床/清洗/全检/缓存/接驳)+ 信号路由改造
|
|||
|
|
3. 仲裁器(单机器人先跑通,接口按多机器人设计)
|
|||
|
|
4. 前端看板 + 人工交互对接
|
|||
|
|
|
|||
|
|
**路线 B:现有代码渐进改造**(若仍需复用 V0.6 资产)
|
|||
|
|
- P0 止血:通用监控框架(full_slot_request / resource_hold_timeout / orphan_broadcast 三条规则)
|
|||
|
|
- P1 模块化封装:设备状态管理拆模块,引擎降级为 job 推进 + 事件路由
|
|||
|
|
- P2 仲裁重构:搬运任务队列 + 目的地预约(修正持料模型)
|
|||
|
|
- P3 终态:模块自治,job 退化为数据
|
|||
|
|
|
|||
|
|
**阶段验收标准(P3)**:
|
|||
|
|
- [ ] 任一设备模块故障/卡死,其他模块继续运转(故障隔离)
|
|||
|
|
- [ ] 全检台满 2 件 100% 自动发 M2102(不依赖任何 job 路径)
|
|||
|
|
- [ ] robot 在任意时刻不处于"持料等待"状态(预约制)
|
|||
|
|
- [ ] 双机器人同时执行任务无冲突(槽位预约串行化)
|
|||
|
|
- [ ] 重启后从事件表完整恢复全部模块状态(无需人工干预)
|