多服务节点数据修正方案设计与实现

文章目录

多服务节点数据修正方案设计与实现

一、背景与问题

在 ERP 系统中,业务人员在录入产品成本时可能发生误操作,导致产品成本数据错误。这类错误数据往往不会立即被发现,而是在最终的统计和核算环节才会暴露问题。然而此时,错误数据可能已经在多个业务领域节点中保存了副本:

  • 成本基础数据节点:存储产品成本原始数据
  • 定价计算节点:基于成本计算销售定价
  • 财务报表节点:基于定价生成财务统计报表
  • 库存核算节点:基于成本进行库存价值核算

这种场景下,我们需要设计一套多服务节点协作的数据修正方案,实现:

  1. 多节点顺序修正:按照节点依赖关系依次修正各节点数据
  2. 可视化进度追踪:实时查看各节点修正进度和状态
  3. 中断可重试:支持节点级别失败重试,保证最终一致性
  4. 事务性保证 :利用事务性发件箱确保消息可靠投递

二、核心设计

在深入实现细节之前,我们首先需要了解系统的整体架构设计。本节将从架构图、核心实体、消息设计三个维度展开说明。

2.1 整体架构图

事务性发件箱
事务性发件箱
事务性发件箱
触发下一节点
失败重试
业务人员创建修正任务
任务编排服务
任务进度表

data_revised_task_process
消息队列层 RocketMQ
节点1 Topic:A
节点2 Topic:B
节点3 Topic:C
节点4 Topic:D
任务完成
成本基础节点
定价计算节点
财务报表节点
库存核算节点
结果回执
更新流程状态
延迟检查机制

2.2 表结构设计

基于上述架构,我们需要设计三张核心数据表来支撑整个修正流程。以下是各表的详细设计:

2.2.1 订正任务表 (data_revised_task)
字段名 类型 说明
id BIGINT 主键ID
task_identifier VARCHAR(64) 任务标识 (雪花算法生成)
revised_type_id BIGINT 订正类型ID
revised_type_identifier VARCHAR(64) 订正类型标识
revised_type_desc VARCHAR(256) 订正类型描述
status INT 任务状态:1-初始化 2-进行中 3-完成 4-暂停
main_task_id BIGINT 主任务ID (多任务关联)
creator_name VARCHAR(64) 创建人
create_time DATETIME 创建时间
2.2.2 订正任务流程表 (data_revised_task_process)
字段名 类型 说明
id BIGINT 主键ID
revised_task_id BIGINT 订正任务ID
revised_task_identifier VARCHAR(64) 任务标识
revised_node_id BIGINT 订正节点ID
revised_node_name VARCHAR(64) 节点名称
revised_node_sort INT 节点排序 (确保顺序执行)
revised_topic VARCHAR(128) 订正topic
revised_group VARCHAR(128) 订正group
revised_tag VARCHAR(64) 订正tag
revised_param TEXT 订正参数 (JSON)
process_status INT 流程状态:1-初始化 2-处理中 3-成功 4-失败 5-延迟处理
delay_check_mark INT 延迟检查标记
create_time DATETIME 创建时间
update_time DATETIME 更新时间
2.2.3 订正节点配置表 (data_revised_node)
字段名 类型 说明
id BIGINT 主键ID
node_name VARCHAR(64) 节点名称
node_sort INT 节点排序
revised_topic VARCHAR(128) 订正topic
revised_group VARCHAR(128) 订正group
revised_tag VARCHAR(64) 订正tag
revised_type_id BIGINT 订正类型ID
delay_check_mark INT 延迟检查标记
delay_check_topic VARCHAR(128) 延迟检查topic
delay_check_group VARCHAR(128) 延迟检查group
delay_check_tag VARCHAR(64) 延迟检查tag
create_time DATETIME 创建时间

2.3 消息设计

消息是驱动整个修正流程的核心载体。我们定义了两种关键消息类型:

任务执行消息 (DataRevisedTaskMessage)
字段名 类型 说明
taskId Long 订正任务ID
taskIdentifier String 任务标识
revisedTypeId Long 订正类型ID
revisedTypeIdentifier String 订正类型标识
revisedTypeDesc String 订正类型描述
dataRevisedTaskProcessId Long 订正任务流程ID
revisedNodeId Long 订正节点ID
revisedNodeName String 节点名称
revisedParam String 订正参数 (JSON)
uniqueKey String 幂等键
执行结果消息 (DataRevisedTaskProcessExecutionResultMessage)
字段名 类型 说明
taskId Long 订正任务ID
taskIdentifier String 任务标识
dataRevisedTaskProcessId Long 订正任务流程ID
enforcementResult String 执行结果:SUCCESS/FAIL
revisedParam String 传递给下一节点的参数
executeDesc String 执行描述
uniqueKey String 幂等键

三、核心流程实现

基于前述的实体和消息设计,本节将详细讲解四个核心流程的实现逻辑。

3.1 任务创建与编排

任务创建是整个流程的起点,需要完成订正类型查询、节点列表获取、任务实体创建、流程记录初始化等工作:

java 复制代码
@Transactional(rollbackFor = Exception.class)
public DataRevisedTask addTask(DataRevisedTaskAddRequest request, Long mainTaskId) {
    
    // 1. 查询订正类型配置
    DataRevisedType revisedType = revisedTypeService.getByTypeIdentifier(request.getTypeIdentifier());
    
    // 2. 查询订正类型关联的节点列表 (按nodeSort排序)
    List<DataRevisedNode> nodeList = revisedNodeService.getByTypeIdentifier(request.getTypeIdentifier());
    
    // 3. 创建订正任务
    DataRevisedTask task = createTaskEntity(revisedType, request.getOperatorName(), mainTaskId);
    this.save(task);
    
    // 4. 创建任务流程记录 (每个节点一条记录)
    List<DataRevisedTaskProcess> processList = new ArrayList<>();
    for (DataRevisedNode node : nodeList) {
        DataRevisedTaskProcess process = new DataRevisedTaskProcess();
        process.setRevisedTaskId(task.getId());
        process.setRevisedTaskIdentifier(task.getTaskIdentifier());
        process.setRevisedNodeId(node.getId());
        process.setRevisedNodeName(node.getNodeName());
        process.setRevisedNodeSort(node.getNodeSort());
        process.setRevisedTopic(node.getRevisedTopic());
        process.setRevisedGroup(node.getRevisedGroup());
        process.setRevisedTag(node.getRevisedTag());
        
        // 头节点设置修正参数
        if (node.getNodeSort() == 1) {
            process.setRevisedParam(request.getRevisedParam());
        }
        process.setProcessStatus(ProcessStatusEnum.INITIALIZATION.getStatus());
        process.setDelayCheckMark(node.getDelayCheckMark());
        processList.add(process);
    }
    processService.saveBatch(processList);
    
    return task;
}

3.2 节点执行与推进

节点执行是驱动工作流流程运转的关键步骤,需要处理状态变更和消息发送:

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void commitTask(String taskIdentifier) {
    
    // 1. 获取头节点 (node_sort = 1)
    DataRevisedTaskProcess headNode = processService.getTaskProcessHeadNode(taskIdentifier);
    
    // 2. 更新任务状态为执行中
    updateTaskStatus(taskIdentifier, TaskStatusEnum.PROCESSING);
    
    // 3. 执行头节点
    executeNode(headNode);
}

private void executeNode(DataRevisedTaskProcess process) {
    
    Long processId = process.getId();
    Integer currentStatus = process.getProcessStatus();
    
    // 状态更新逻辑
    if (ProcessStatusEnum.INITIALIZATION.getStatus().equals(currentStatus)) {
        // 正常发起:初始化 -> 处理中
        processService.modifyStatus(processId, ProcessStatusEnum.PROCESSING, 
                ProcessStatusEnum.INITIALIZATION);
    } else if (ProcessStatusEnum.PROCESSING_FAILED.getStatus().equals(currentStatus)) {
        // 失败重试:失败 -> 处理中
        processService.modifyStatus(processId, ProcessStatusEnum.PROCESSING, 
                ProcessStatusEnum.PROCESSING_FAILED);
    } else if (ProcessStatusEnum.PROCESSING_SUCCEEDED.getStatus().equals(currentStatus)) {
        // 延迟重试:成功 -> 延迟处理中
        processService.modifyStatus(processId, ProcessStatusEnum.DELAY_PROCESSING, 
                ProcessStatusEnum.PROCESSING_SUCCEEDED);
    }
    
    // 发送执行消息
    sendExecutionMessage(process);
}

3.3 结果回执与下一节点触发

每个节点执行完成后,需要处理执行结果并触发下一节点:

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void handleExecutionResult(DataRevisedTaskProcessExecutionResultMessage result) {
    
    Long processId = result.getDataRevisedTaskProcessId();
    
    if (EnforcementResultEnum.SUCCESS.getResult().equals(result.getEnforcementResult())) {
        // 执行成功
        processService.modifyStatus(processId, ProcessStatusEnum.PROCESSING_SUCCEEDED,
                ProcessStatusEnum.PROCESSING, ProcessStatusEnum.DELAY_PROCESSING);
        
        // 更新下一节点修正参数
        processService.updateNextProcessParam(result.getTaskIdentifier(), processId, 
                result.getRevisedParam());
        
        // 触发下一节点执行
        executeNextNode(result.getTaskIdentifier(), processId);
        
    } else {
        // 执行失败
        processService.modifyStatus(processId, ProcessStatusEnum.PROCESSING_FAILED,
                ProcessStatusEnum.PROCESSING, ProcessStatusEnum.DELAY_PROCESSING);
    }
}

private void executeNextNode(String taskIdentifier, Long currentProcessId) {
    
    // 查询下一节点
    DataRevisedTaskProcess nextNode = processService.getTaskProcessNextNode(taskIdentifier, currentProcessId);
    
    if (nextNode == null) {
        // 无下一节点,任务完成
        updateTaskStatus(taskIdentifier, TaskStatusEnum.COMPLETE);
        checkAssociatedTask(taskIdentifier);
        return;
    }
    
    // 执行下一节点
    executeNode(nextNode);
}

3.4 延迟检查机制

为了处理节点执行超时但未明确失败的情况,我们引入了延迟检查机制:

java 复制代码
private void sendDelayCheckMessage(DataRevisedTaskMessage message, DataRevisedTaskProcess process) {
    
    String uniqueKey = message.getRevisedTypeIdentifier() + "-DELAY-CHECK-" + 
            message.getTaskIdentifier() + "-" + process.getId();
    message.setUniqueKey(uniqueKey);
    
    // 发送延迟消息 (30分钟后触发)
    RocketMQProducerLog log = RocketMQProducerLog.newBuilder()
            .uniqueMsg(uniqueKey)  // 事务性发件箱的方法名仍是 uniqueMsg
            .topic(process.getDelayCheckTopic())
            .groupId(process.getDelayCheckGroup())
            .tag(process.getDelayCheckTag())
            .body(JSONObject.toJSONString(message))
            .type(MQMessageTypeEnum.DELAY_MESSAGE)
            .delayTime(DateUtil.offsetMinute(new Date(), 30).getTime())
            .build();
    
    rocketMQProducerLogService.addProducerLog(log);
}

四、可视化设计

为了让业务人员直观地了解修正任务的执行状态,我们设计了多维度的可视化展示方案。

4.1 任务状态展示

状态 说明 颜色标识
初始化 任务刚创建,待执行 灰色
进行中 正在执行修正 蓝色
完成 所有节点修正完成 绿色
暂停 被手动暂停 橙色

4.2 节点进度展示

节点状态 说明 颜色标识
初始化 待执行 灰色
处理中 正在执行 蓝色
处理成功 修正完成 绿色
处理失败 执行失败,可重试 红色
延迟处理中 等待延迟检查 黄色

4.3 页面展示示例

页面设计采用卡片式布局,主要包含任务详情信息和节点进度两个部分。

任务详情

|------|----------------------------|
| 任务标识 | 20241102103055001234567890 |
| 订正类型 | 产品成本修正 |
| 创建人 | 张三 |
| 创建时间 | 2024-11-02 10:30:55 |
| 任务状态 | 进行中 |

节点进度

排序 节点名称 处理状态 更新时间
1 成本基础 ✅ 完成 2024-11-02 10:31:05
2 定价计算 🔄 处理中 2024-11-02 10:31:10
3 财务报表 ⏳ 等待 -
4 库存核算 ⏳ 等待 -

⚠️ 当前节点: 定价计算 (处理失败 - 可重试)
错误信息: 数据库连接超时
🔄 重试当前节点 ⏸ 暂停任务 🔍 查看详情

五、失败重试机制

分布式环境下,节点执行失败是不可避免的情况。本方案设计了多层次的重试策略来保证最终一致性。

5.1 重试策略

节点执行失败
重试机制
手动重试
点击重试按钮

手动触发
延迟检查重试
delayCheck=1

30分钟后检查
关联任务重试
主任务触发

5.2 重试API

java 复制代码
@PutMapping("/retryNode")
public Result<Void> retryNode(Long nodeId) {
    DataRevisedTaskProcess process = processService.getById(nodeId);
    DataRevisedTask task = taskService.getById(process.getRevisedTaskId());
    taskService.executeNode(task, process);
    return Result.success();
}

六、事务性发件箱集成

本方案复用事务性发件箱 模式确保消息可靠投递,详见 《事务性发件箱模式设计与实现》

6.1 消息幂等设计

消息幂等的核心在于幂等键的设计。由于消息会因重试、网络波动等导致重复发送,幂等键必须满足:同一个业务上下文下,相同消息的幂等键应保持一致

幂等键设计原则
消息类型 幂等键组成 说明
任务执行消息 TASK_EXECUTE-{taskIdentifier}-{processId} 头节点首次执行触发
执行结果消息 TASK_RESULT-{taskIdentifier}-{processId} 各节点执行完成结果回执
延迟检查消息 TASK_DELAY_CHECK-{taskIdentifier}-{processId} 延迟检查触发

七、总结

7.1 核心能力

能力 实现方式
多节点顺序推进 nodeSort排序 + 结果回执驱动
可视化进度追踪 任务状态 + 节点状态多维度展示
失败可重试 手动重试 + 延迟检查 + 状态机
事务性保证 事务性发件箱模式

7.2 方案优势

  1. 节点解耦:各节点通过消息队列通信,互不依赖
  2. 进度可视化:实时查看各节点修正状态
  3. 失败无感:节点级别失败重试,不影响其他节点
  4. 可靠投递:事务性发件箱确保消息不丢失
  5. 可扩展:新增节点只需配置,无需修改代码

7.3 适用场景

  • 多服务节点数据修正
  • 分布式数据同步
  • 跨服务数据对账
  • 批量数据修复任务
相关推荐
ch.ju2 小时前
Java程序设计(第3版)第二章——局部变量
java
朱一头zcy2 小时前
Java基础复习10:Java网络编程入门、Junit单元测试、反射基本介绍、注解基本介绍、XML基本介绍
java·笔记
user_admin_god2 小时前
Opencode常见问题与优化排查
java·人工智能·自然语言处理·nlp·idea
工作log2 小时前
从 Ubuntu 22.04 到 ROS 2 Humble 完整环境搭建与 Java 控制指南
java·linux·ubuntu
Wenzar_2 小时前
**元宇宙经济中的智能合约与数字资产:基于Solidity的NFT交易平台开发实践**随着元宇宙概念持续升
java·python·区块链·智能合约
Giggle12182 小时前
从零解构一套校园外卖系统:架构设计、技术选型与核心难点剖析
java·运维·微服务
一叶飘零_sweeeet2 小时前
Spring Boot 4.0:云原生 Java 开发的范式革命
java·spring boot·云原生
Devin~Y2 小时前
大厂 Java 面试实战:Spring Boot 微服务 + Redis 缓存 + Kafka 消息 + Kubernetes + RAG(小Y水货翻车记)
java·spring boot·redis·kafka·spring security·jwt·oauth2
朱一头zcy2 小时前
设计模式入门:简单认识单例模式、模板方法、工厂模式、装饰模式、动态代理
java·设计模式