从需求到上线:聊聊技术探索与实践中的那点事儿

向量宇航员
2025-06-12 00:15
阅读 272

开篇

开篇

作为一名全栈开发工程师,这些年我经历了很多项目,也踩过不少坑。有些是技术本身的局限性,有些是团队沟通的误解,更多的则是对业务理解不够深入导致的技术方向偏差。

今天想和大家聊聊一个让我印象深刻的项目——我们当时要为一个电商后台系统重构订单流程模块。这个模块虽然不是最核心的部分(比如支付、库存),但它贯穿了用户下单、物流处理、售后等多个环节,牵一发而动全身。

我想通过这次实践,分享一下我在技术选型、问题定位、方案实现、上线过程以及踩坑经验等方面的一些体会。希望对你在日常工作中有所启发。


问题描述:旧系统痛点初现

问题描述:旧系统痛点初现

我们的电商平台已经运行了好几年,订单处理流程一开始是快速迭代开发出来的。随着业务增长,代码结构变得越来越混乱,维护起来异常吃力。

具体表现包括:

  • 订单状态流转逻辑复杂且耦合严重
  • 多个服务之间依赖不清晰,经常出现循环调用
  • 异步任务队列堆积严重,部分订单处理延迟高达十几分钟
  • 日志信息分散,排查问题困难
  • 没有良好的失败重试机制,偶发网络抖动就会导致订单卡住

老板一句话:“这模块早晚炸。”

所以我们决定趁一次大促前重构它。目标很明确:让订单流程更健壮、可扩展、易维护,同时提升整体吞吐能力。


技术选型与架构设计

技术选型与架构设计

首先得确定技术栈。由于之前使用的是 Spring Boot + MySQL + RabbitMQ 的组合,我们倾向于沿用这套体系,但在新架构上做了调整:

  • 语言层面:继续使用 Java,生态成熟,利于维护
  • 异步任务调度:引入 Quartz 做定时补偿,但发现它不适合我们这种事件驱动的场景,最后改成了 Kafka+自定义状态机
  • 状态管理:设计了一个基于状态机的订单流程模型,将整个生命周期抽象成“创建 -> 支付成功 -> 已发货 -> 完成”等状态
  • 流程引擎:调研了 ActivitiCamunda,但由于定制化成本高,最终选择了轻量级的状态机库:Squirrel-foundation

系统架构设计-1

整体架构示意图如下:

前端/APP
   ↓
API Gateway
   ↓
订单服务(Order Service)
   ↘                          ↘
    库存服务      ← 消息队列(Kafka) ← 状态变更通知
   ↗                          ↗
支付服务、物流服务

整个系统由订单服务作为核心控制流,其他服务通过订阅 Kafka 消息进行相应动作,降低耦合度,提高响应速度。


关键实现思路

1. 状态机驱动订单流程

状态机的核心思想是将业务逻辑抽象成“状态 + 迁移条件”,非常适合这种流程强规则的场景。

我们在 Order 实体中维护两个字段:

private String currentState; // 当前状态,如 CREATED, PAID, DELIVERED...
private String currentEvent; // 触发当前状态的事件,如 PAY_SUCCESS, SHIPMENT_DONE...

每个状态迁移都有对应的 handler 来处理业务逻辑,例如支付完成后的库存扣减、物流信息同步等。

示例代码片段:

public class OrderStateMachine extends AbstractStateMachine<OrderState, OrderEvent> {
    
    public OrderStateMachine(Order order) {
        super(order.getCurrentState(), Map.of(
            CREATED, Set.of(PAY_SUCCESS),
            PAID, Set.of(SHIPPED),
            SHIPPED, Set.of(COMPLETED)
        ));
        
        addHandler(PAY_SUCCESS, this::handlePaymentSuccess);
        addHandler(SHIPPED, this::handleShipped);
    }


![技术对比分析-2](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061200/0360d443-867e-4b5c-a97f-b0d9a0ec737e.jpg)


    private void handlePaymentSuccess(Order order) {
        // 扣减库存
        inventoryService.deductStock(order.getProductId());
        // 更新订单时间
        order.setPaidAt(new Date());
    }

    private void handleShipped(Order order) {
        // 同步到物流系统
        logisticsService.updateStatus(order.getTrackingNumber(), "IN_TRANSIT");
    }
}

这样的设计非常直观,后续新增状态只需要加对应的 handler 即可,代码可维护性强很多。


2. 使用 Kafka 解耦订单与外部服务

之前的系统中,订单完成后直接调用库存服务和物流服务,接口超时或失败会直接阻塞订单主流程。

改为异步后,订单服务只负责发布 Kafka 消息:

void publishEvent(String topic, OrderEvent event) {
    kafkaTemplate.convertAndSend(topic, event);
}

各服务监听自己的 Topic,做异步处理,并允许失败重试。这样大大提升了系统的稳定性。


3. 自动补偿机制的引入

为了应对消息丢失或消费失败的情况,我们还加入了一个补偿 Job,每五分钟检查一次所有“处于中间态”的订单,重新触发对应 Event。

这部分是使用 Spring Scheduler 实现的:

@Scheduled(fixedRate = 5 * 60 * 1000)
public void checkStuckOrders() {
    List<Order> stuckOrders = orderRepository.findByCurrentStateIn(List.of("PAID", "SHIPPED"));
    for (Order order : stuckOrders) {
        try {
            retryDispatcher.dispatch(order.getCurrentEvent(), order);
        } catch (Exception e) {
            log.error("Failed to dispatch retry event for order: {}", order.getId(), e);
        }
    }
}

这个 Job 可以部署多个实例,利用分布式锁保证同一时间只有一个节点在执行。


踩坑经验分享

重构过程中我们也不是一帆风顺,遇到一些有意思的问题,分享几个印象深的:

1. Kafka 分区导致的消息顺序问题

某个阶段我们发现同一个订单的多个事件被分配到不同分区,导致消费顺序错乱。

解决方式:按 orderId 做 hash,确保同一个订单的消息落到同一个 partition:

int partition = Math.abs(orderId.hashCode()) % numPartitions;

这样 Kafka 就能保证单个订单的消息顺序一致。

2. 状态机状态不一致

在并发请求下,比如同时有两个线程尝试更新订单状态,会导致状态不一致。

解决方案是给订单状态更新加上乐观锁:

UPDATE orders SET current_state = 'SHIPPED' WHERE id = ? AND current_state = 'PAID';

返回受影响行数判断是否更新成功,否则抛出异常并重试。

3. 状态机配置错误引发死循环

刚开始没有对状态转移图做校验,结果出现了 A → B → A 的死循环。

解决办法是在初始化时做拓扑排序,检测环路并提前报错:

if (hasCycle(stateGraph)) {
    throw new IllegalStateException("状态转移图存在环路,请检查状态配置");
}

上线效果与收益

重构完成上线后,整个系统的表现有了明显提升:

指标 重构前 重构后
平均订单处理耗时 8s 2.3s
每秒处理订单数(QPS) 120 450
状态异常订单占比 0.7% <0.1%
故障恢复时间(MTTR) 30min <5min

此外,代码结构更清晰,新功能接入更快,运维同学也反馈日志追踪更容易了。


我的几点建议与经验总结

1. 技术选型别盲目追新

我们一开始考虑过用 BPMN 流程引擎,但后来发现轻量级的状态机反而更适合当前业务。适合比先进更重要。

2. 提前规划可观测性

监控、日志和链路追踪在重构中起到了关键作用。我们用了 Prometheus + Grafana 做实时监控,SkyWalking 做链路追踪,方便定位问题。

3. 保持技术债的敏感度

早期追求快,往往会埋雷。一定要及时评估哪些技术债可以承受,哪些必须尽早偿还,否则代价更高。

4. 多跟上下游服务对接人沟通

状态机的设计如果不与其他服务协商好,很容易变成理想模型。我们多次因为“我以为你们这边应该完成了”而返工。

5. 小步演进胜过一次性重构

如果一开始就想着把整个模块全部重构一遍,风险很大。我们采用的是“逐步替换 + 新老共存”的方式,先拆分流程,再优化细节,最后统一升级。


写在最后

技术探索从来都不是一件轻松的事,尤其是在真实业务场景下,我们面对的不只是性能和架构,还有协作、沟通、取舍和落地。

这篇文章讲的是一个小小的订单流程重构,但背后涉及的知识点却涵盖了服务治理、异步通信、状态管理、容错处理等多个方面。我希望通过这次实战的分享,能带给你一些思考和启发。

如果你也有类似的经历或者疑问,欢迎留言交流~毕竟,技术的本质,就是不断实践,不断探索。


🎯 Tech Tip
如果你正在考虑重构某个复杂的业务流程,不妨尝试用状态机来抽象逻辑,配合 Kafka 或 RocketMQ 做事件解耦,你会发现系统瞬间清爽了不少。

祝你在技术之路上越走越远!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝