从一次重构说起:如何在技术探索中做出真正有价值的选择
引言:一次“小题大做”的重构尝试

去年我在一家中型互联网公司负责一个后端业务模块的优化工作。当时的项目是一个面向中小商家的线上订单管理系统,用户量不算大但并发请求频繁,尤其是高峰期经常会出现接口超时、数据库连接池被打满等问题。
项目上线已有两年,代码库已经积累了相当的复杂度。我接手的时候,技术栈主要是 Node.js + Express + Sequelize + MySQL,前端是 React,部署在阿里云 ECS 上,使用 Nginx 做负载均衡。
老板说:“这个系统现在撑得过去年双十一流量,但再往上走肯定不行了。你看看能不能重构一下,顺便试试有没有更合适的架构。”
这句话听起来挺轻巧的,但背后其实藏着很多现实问题。我当时也觉得不就是个模块重写嘛,干就完了。结果整个过程比我预想的要曲折得多,但也因此收获了很多对“技术探索与实践”的深刻理解。
问题描述:为什么一定要重构?

系统现状的问题
- 接口响应时间不稳定,尤其在高峰期,大量请求超时
- 数据层封装不够合理,Sequelize 的模型嵌套复杂,维护成本高
- 日志记录混乱,出现问题排查困难
- 扩展性差,新增一个业务逻辑往往需要改动多个地方
- 没有监控体系,出了问题全靠被动反馈
这并不是简单的性能瓶颈问题,而是整体结构设计带来的连锁反应。我们当时面临两个选择:
- 就地修修补补,加缓存、做异步、优化 SQL;
- 架构层面调整,引入服务拆分、模块化改造。
最终我们选择了后者 —— 因为从长远来看,系统本身的可维护性和扩展性才是最关键的。但这一步一旦踏出去,意味着我们要面对一系列全新的挑战。
解决方案:从 Monolith 走向微服务化

技术选型的思考
我们决定不再局限于原有的框架和模式,转而尝试基于 Node.js + NestJS + TypeORM + Redis + RabbitMQ 的方式来构建一个新的核心服务模块。
选择 NestJS 是因为其模块化的设计非常契合我们想要的组织结构;TypeORM 相比 Sequelize 在 TypeScript 支持方面更成熟;Redis 和 RabbitMQ 则是用来解决缓存和异步任务处理的问题。
此外,我们还开始尝试用 Docker 部署,并借助 Kubernetes 实现服务编排。虽然当时团队里没有 K8s 经验,但我们愿意付出学习成本来换取长期的技术红利。
设计思路
我们采用了**领域驱动设计(DDD)**的思路,将系统划分成几个核心服务:
- 用户服务:负责用户信息管理
- 订单服务:核心业务逻辑处理
- 通知服务:处理推送和消息发送
- 缓存服务:统一对外提供缓存能力
通过引入**接口网关(API Gateway)**来聚合各个服务的 API,前端只需对接网关即可。
这种架构变化让我们能更好地实现职责隔离,也让每个模块更加清晰可控。
代码实践:从旧代码到新架构的迁移

这里以订单服务为例,展示一下我们在重构中的一些关键代码变化。
原始代码片段(Express + Sequelize)
// routes/order.js
router.post('/create', async (req, res) => {
try {
const order = await Order.create(req.body);
const user = await User.findByPk(order.userId);
if (!user) throw new Error('User not found');
// 更多复杂的嵌套逻辑...
sendNotificationToUser(user.id, 'Your order is created');
res.json(order);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
这段代码看似简单,但实际上隐藏着巨大的耦合风险:创建订单的同时处理用户验证、调用通知等等,违反了单一职责原则。
新架构下的实现(NestJS)
// create-order.dto.ts
export class CreateOrderDto {
readonly userId: number;
readonly productIds: number[];
}
// order.controller.ts
@Post()
async createOrder(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto);
}
// order.service.ts
@Injectable()
export class OrderService {
constructor(
private readonly orderRepo: OrderRepository,
private readonly userClient: UserServiceClient,
private readonly eventPublisher: EventPublisher
) {}
async create(dto: CreateOrderDto) {
const user = await this.userClient.getUserById(dto.userId);
if (!user) throw new NotFoundException('User not found');
const order = this.orderRepo.create(dto);
await this.orderRepo.save(order);
this.eventPublisher.publish(new OrderCreatedEvent(order.id));
return order;
}
}
这种写法不仅结构清晰,而且把责任进行了合理的分离,便于后期测试和维护。
踩坑经验:别让新技术变成新负担
1. RabbitMQ 消费堆积
初期我们在订单服务发布事件后通过 RabbitMQ 广播给通知服务。但在压测过程中发现,当并发量上万时,队列出现了严重堆积。
原因分析: 默认的消费者确认机制未开启手动提交,导致部分消息被重复消费甚至丢失。
解决方案: 开启手动 ACK,并增加消费者并发数,配合死信队列(DLQ)做异常兜底。
// 使用 amqplib 的示例代码
channel.consume(queueName, async msg => {
try {
const data = JSON.parse(msg.content.toString());
await handle(data);
channel.ack(msg);
} catch (err) {
console.error('failed to process message:', err);
channel.reject(msg, false); // 进入 DLQ
}
}, { noAck: false });
2. NestJS 的依赖注入陷阱
NestJS 的 DI 确实强大,但也有坑。比如,我们在某些 Service 中引入了全局配置类,结果在单元测试时发现配置读取不到。
根本原因: 测试环境下模块注册不完整,导致依赖注入链条断裂。
解决办法: 明确依赖项,并在 TestingModule 中模拟相关 Provider。
效果总结:系统焕然一新,团队更有信心了
重构完成后,我们做了全面的压测和灰度上线:
- QPS 提升了约 3.5 倍
- P99 接口响应时间下降了 60%
- 线程阻塞情况基本消失
- 新功能开发效率提高了 40%
更重要的是,我们建立了一套规范化的服务通信机制和日志追踪体系,后续维护成本大大降低。
经验分享:技术探索的底线是“解决问题”,不是“炫技”
不要为了“重构”而重构
很多人看到系统老旧就想推翻重来,但如果你只是为了追求“先进性”或者“简历好看”,那很可能适得其反。真正的重构应该是为了提升系统的稳定性、可维护性、可扩展性。
合理评估技术成本
我们当初为了推动 K8s 化部署,花了整整一个月时间搭建环境、调试配置。中间几次差点放弃,但最后证明这笔账是值得的。不过你也得考虑当前团队是否有足够的能力和人力来支撑这个投入。
多用工具,少造轮子
在整个过程中,我们并没有自己写 ORM 或者消息总线,而是选择成熟的开源工具组合。比如:
- 使用 Winston 做日志管理
- Prometheus + Grafana 做监控
- OpenTelemetry 做链路追踪
- ESLint + Prettier 做代码规范
这些工具帮助我们快速建立起一套稳定的开发运维流程,避免了不必要的重复劳动。
给正在探索的你几点建议
- 带着问题去探索:别盲目学技术,而是先想清楚你在实际工作中遇到了哪些痛点。
- 小范围先行试点:可以先在某一个模块或服务中尝试新技术,验证后再推广。
- 关注落地成本:有时候“更优解”并不一定适合你的项目阶段,权衡好短期收益与长期成本。
- 文档和沟通是关键:新技术引入后,一定要同步更新文档,确保团队成员都能理解并接受。
- 保留回滚路径:任何重构都存在风险,务必提前做好备份和降级预案。
写在最后:技术探索是一场修行
这次重构让我深刻体会到:技术从来不是冰冷的代码和框架,它是我们面对复杂业务需求时的一种“思维方式”。
每当我们站在技术路口做选择时,不仅要问“这个好不好用”,更要问“这个是否真的能帮我们解决问题”。那些深夜查文档、改配置的日子也许很累,但它们最终沉淀成了我们的经验和底气。
希望这篇文章能给你一些启发,愿你也能在自己的技术旅途中找到属于自己的答案。共勉!

评论 0