技术探索的边界:从一个分布式项目说起
引子:为什么我们需要技术探索?

我是一名后端架构师,平时的主要工作是为业务系统设计可扩展、稳定、高性能的架构。在从业多年的过程中,我遇到过很多棘手的问题,但其中一次让我印象特别深刻,也彻底改变了我对技术探索与实践的理解。
那是一个典型的中大型互联网公司的场景:我们正在搭建一个新的供应链管理平台,初期看起来需求简单明了,但随着用户量的增长和复杂业务逻辑的叠加,系统的性能瓶颈很快显现出来。这个问题推动我深入地进行了一次技术探索,尝试用不同的方案来解决问题,并从中汲取了宝贵的经验。
这篇文章我想通过这段经历,结合自己的实战经验,讲一讲“技术探索”的背后逻辑、方法论以及我们在面对实际问题时的一些思路和取舍。希望能带给大家一些启发和帮助。
项目背景简要介绍

我们的新系统主要是面向集团内部的采购部门,用来对接多个供应商的数据。初期目标很明确:
- 提供数据接口给上游业务使用
- 支持高并发访问(预期 QPS 3000+)
- 数据一致性要求高
- 要能支持快速迭代
听起来像是一个常规的 API 服务 + 分库分表的结构。但我们忽略了一个重要的点——业务流程本身具有高度异步性,而这一点最终导致系统在上线初期就频繁出现请求阻塞、数据库连接池占满、超时等问题。
更尴尬的是,这些问题不是一次性爆发,而是随着并发数逐渐攀升慢慢显现出来的。一开始大家以为是代码逻辑优化没到位,但改了几轮之后仍然没有根本性好转。于是,我们必须开始一场技术探索之旅。
遇到的具体挑战
1. 同步模型带来的瓶颈
最核心的问题出在一个同步调用链上。
例如:一个创建订单的接口,在完成数据写入后,需要触发两个外部系统的回调通知,这两个系统必须都返回“成功”才能判定整个流程完成。这个过程如果其中一个系统响应慢,或者出现网络抖动,就会阻塞整个线程资源,导致大量请求排队等待。
这个问题的后果是:
- 接口平均响应时间从 <200ms 突然增加到 >5s
- CPU 利用率飙升至 90%+
- Tomcat 的最大连接数被打满,部分请求直接超时被丢弃
2. 数据库压力过大
由于每个操作都会产生多张表的联动修改,同时还要做状态更新、日志记录,最终导致 MySQL 集群经常出现慢查询,锁竞争加剧。
3. 缺乏监控和诊断手段
早期监控主要依赖 Prometheus 和 Grafana,但对于某些关键路径缺乏细粒度的追踪能力,这使得我们很难定位到底是哪个环节卡住了。
我们是怎么解决的?
针对以上三个痛点,我们采取了一系列渐进式的改进策略,下面详细讲讲我们的解决思路。
第一步:引入异步处理机制
首先,我们要做的就是打破同步模型,把那些非核心链路的操作拆出去。比如,原本在主流程中执行的两个回调系统,被我们抽离成任务投递到消息队列中。
技术选型对比:
| 方案 | 特点 | 使用成本 | 可靠性 | 备注 |
|---|---|---|---|---|
| RabbitMQ | 消息确认机制完善,延迟低 | 中等 | 高 | 社区活跃,适合中小规模部署 |
| Kafka | 吞吐高,适用于大数据管道 | 较高 | 高 | 需维护 Zookeeper |
| RocketMQ | 国内社区强,顺序消息支持好 | 偏高 | 高 | 对 Java 生态友好 |
考虑到我们已经在使用 RocketMQ,而且团队有相关的运维经验,最终决定采用 RocketMQ 来承载这些异步任务。
关键改造步骤:
- 主流程只保留核心业务逻辑;
- 将回调操作封装成事件对象,发送到 MQ;
- 单独启动若干个 Worker 进程消费这些事件;
- 加入失败重试机制,并将失败信息落库以便后续人工干预。
示例代码片段:
public void createOrder(OrderDTO dto) {
// 核心数据库操作
Order order = orderService.create(dto);
// 发送异步事件
AsyncEvent event = new AsyncEvent();
event.setOrderId(order.getId());
event.setType(AsyncEventType.NOTIFY_SUPPLIER_A);
mqProducer.send(event);
AsyncEvent event2 = new AsyncEvent();
event2.setOrderId(order.getId());
event2.setType(AsyncEventType.NOTIFY_SUPPLIER_B);
mqProducer.send(event2);
}
消费者部分简化如下:
@RocketMQMessageListener(topic = "ASYNC_EVENT_TOPIC", consumerGroup = "async-consumer-group")
public class AsyncConsumer implements MessageListener<OrderEvent> {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
try {
AsyncEvent event = parse(msg.getBody());
handleEvent(event); // 具体执行逻辑
} catch (Exception e) {
log.error("处理异步事件失败: {}", e.getMessage());
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
第二步:数据库读写分离
为了缓解数据库的压力,我们对数据库做了主从分离,所有的写操作走主库,读操作走从库,并且配置了合理的负载均衡策略。
实现方式:
- 使用 MyCat 或 ShardingSphere 中间件做读写分离代理
- 在 DAO 层根据 SQL 类型自动切换数据源
这样可以有效降低主库的 IO 压力,提高整体吞吐能力。
第三步:引入分布式事务框架
因为存在跨系统的一致性要求,原来的本地事务已经无法满足需求。我们评估之后,选择了 Seata 作为我们的分布式事务中间件。
其原理大致是通过全局事务 ID 和分支事务的协调,确保所有涉及的服务要么一起提交,要么一起回滚。
不过 Seata 的接入也需要一定的改造成本,比如需要在服务接口加注解 @GlobalTransactional,并且底层数据库要使用 AT 模式或 TCC 模式,都需要适配。
我们当时采用了 AT 模式,因为它侵入性小,适合短期改造。但从长远来看,TCC 更可控、性能更好,只是开发成本更高。
踩过的坑与反思
虽然方向正确,但在落地过程中我们还是踩了不少坑。
坑 1:异步任务堆积
刚开始生产环境上线后,消息积压严重,监控发现消息队列里堆积了好几十万条任务。排查发现是消费者的并发数太少,而每条任务执行耗时较长,且每次拉取的消息数量不够。
解决方案:
- 提升 Consumer 的并行度(增加线程池大小)
- 控制每次拉取的消息数(避免 JVM 内存占用过高)
- 增加失败重试的最大次数限制,并加入死信队列
坑 2:数据库连接未释放
有一个模块在处理事件回调时,忘记关闭数据库连接,导致连接池被耗尽。线上表现是服务几乎不可用,所有接口超时。
教训:
- 所有数据库访问必须用 try-with-resources 包裹
- 增加 DB 监控指标,如当前连接数、空闲连接数等
坑 3:分布式事务中的异常情况未处理
有一次在测试环境下,某个服务节点突然宕机,导致整个事务处于“悬而未决”状态。后来发现是我们没有及时上报异常,也没有对挂起事务做补偿。
改进措施:
- 引入定时补偿 Job
- 增加事务超时设置和异常自动回滚机制
实施后的效果
经过这一系列的改进,系统的稳定性有了显著提升:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 接口 P99 延迟 | 6500ms | 280ms |
| 整体 QPS | ~1800 | ~4500 |
| 数据库连接数 | 不稳定,偶发爆掉 | 稳定控制在可用范围内 |
| 日志错误数 | 平均每天数百条 | 几乎归零 |
更重要的是,团队成员对于异步编程、消息中间件的使用、分布式事务的理解都有了明显提升。这种技术能力的积累,在后续新项目的推进中起到了很大作用。
一些经验分享
作为一名参与过多个项目的技术人,我想总结几点在这次技术探索中的体会和建议:
1. 技术选型别盲目追求“热门”
当时有人建议我们试试 Apache Pulsar,说比 Kafka 性能更好。但我坚持认为“现有工具够用的前提下,优先复用已有系统”。毕竟,新技术意味着学习成本、适配风险和额外的维护压力。
结论: 技术选型要考虑团队现有基础、运维能力、生态完整度,不求最新,但求最合适。
2. 异步化是解决性能问题的第一把刀
这次重构之所以取得成效,很大程度上是因为我们打破了传统的同步思维模式,敢于把一些非核心逻辑剥离出来。这种方式不仅提高了性能,也提升了系统的健壮性。
3. 监控必须前置,而不是出了问题才补
事后加监控永远不如一开始就埋好观测点。像接口耗时、线程数、JVM GC 情况、MQ 消费进度这些指标,一定要尽早接入 APM 工具(如 SkyWalking、Pinpoint)。
4. 分布式事务不是万能药
很多人一上来就想搞 Saga、TCC、两阶段提交,其实大多数场景下只要做好幂等性和异步补偿就够了。真正的强一致性场景其实是少数。
建议: 先问自己:“这个场景下不能容忍脏数据吗?” 如果可以接受短暂不一致,就不需要用分布式事务。
5. 把问题交给机器,把思考留给人类
自动化监控、健康检查、限流熔断这些机制,都可以大大减轻人的负担。我们应该花更多时间在业务理解和技术方案的设计上,而不是疲于应对各种故障。
结语:探索与实践是一体两面
回顾这次技术探索的过程,我觉得最宝贵的不是用了什么组件,而是那种不断寻找答案、验证假设、调整方案的能力。这种能力,才是架构师真正应该具备的核心竞争力。
技术是在变的,框架也是在更新的,但解决问题的思维、权衡利弊的能力、持续学习的态度,才是真正让一个工程师走得远的关键。
如果你也在做类似的工作,或者正在面临系统架构上的挑战,希望这篇文章能带给你一点灵感和信心。
愿你在每一个深夜敲代码的时候,不只是在写一行行语法正确的代码,更是在构建一个稳定可靠、优雅高效的世界。
共勉!

评论 0