微服务架构设计实战:从单体到分布式
背景介绍:为什么我们要拆微服务?

去年年初,我接手了一个公司内部的电商平台重构项目。这个平台原本是一个典型的单体架构系统,使用Spring Boot开发,所有的功能模块都部署在一个应用中,数据库是MySQL单实例。随着用户量增长和业务功能不断增加,系统的问题也逐渐暴露出来。
最明显的表现是:
- 发布新版本需要全量重启,影响所有功能;
- 代码库越来越大,团队协作困难,每次合并分支就像玩俄罗斯轮盘;
- 数据库表之间关联复杂,查询越来越慢;
- 某个功能出问题时整个系统容易雪崩(比如一个接口卡死导致线程池耗尽);
- 新业务需求推进缓慢,小改动也需要协调多个小组。
我们当时意识到:这个系统已经到了“不得不拆”的阶段了。
于是,“微服务架构升级”成为当年的头等大事。
挑战来了:拆服务真的那么容易吗?


理想很丰满,现实却非常骨感。
当我们开始讨论如何拆分服务的时候,第一个难题就摆在眼前:怎么拆?按什么维度?
一开始我们尝试按照业务功能来划分,比如订单、商品、库存、会员等等。听起来逻辑清晰,但实际操作中发现很多功能之间的边界并不那么明确。
比如:
- 一个优惠券发放到底是属于会员服务还是促销服务?
- 用户下单的过程中,涉及库存扣减、积分变动、优惠计算,这些都需要跨服务调用。
更头疼的是:
- 有的老模块没有详细的文档,没人说得清楚它到底在做什么;
- 原有系统的数据库是一张大表,各种外键交叉引用,根本拆不动;
- 团队成员对微服务的认知水平参差不齐,有的同学还在纠结要不要用Dubbo还是Spring Cloud……
那段时间,我们天天开会讨论,经常吵得面红耳赤。
我们是怎么破局的?

第一步:画清边界,搞清楚数据流
我们决定先从梳理现有系统开始:
- 所有关键业务流程跑一遍,记录每个环节涉及的数据和功能;
- 使用UML绘制各个模块的依赖关系图;
- 邀请产品和业务负责人一起参与,定义业务领域边界;
- 结合DDD(领域驱动设计)思想,识别聚合根和上下文边界。
这一步虽然花了不少时间,但效果很好。我们最终确定了以下几个核心服务:
product-service:处理商品信息、SKU管理;order-service:负责订单生命周期管理;inventory-service:处理库存变更;member-service:用户账户与会员等级;coupon-service:优惠券与促销活动;payment-service:支付相关逻辑。
同时,我们也保留了一些公共组件,比如日志、权限、配置中心等。
第二步:技术选型与基础设施准备
我们选择了基于 Spring Cloud Alibaba + Nacos + Seata + Feign + Redis + RocketMQ 的技术栈,原因如下:
- Spring Cloud生态成熟,社区活跃;
- Nacos作为服务注册与配置中心,方便统一管理;
- Seata用于支持分布式事务(虽然不是一开始就全面用,但提前规划了);
- Feign实现服务间通信,简化调用过程;
- Redis用来做缓存和部分状态同步;
- RocketMQ处理异步消息,解耦服务之间强依赖。
第三步:数据库拆分策略
这是最难的一部分。
我们采取了以下措施:
- 按服务独立建库:每个服务对应自己的数据库,互不共享;
- 读写分离:主库负责写,从库提供只读访问;
- 数据同步机制:通过Binlog或定时任务进行数据同步;
- 冗余字段设计:为减少跨服务查询,适当冗余一些字段(如订单中保留商品名称);
- 迁移策略:使用ETL工具逐步迁移数据,避免直接下线原库。
举个例子,在订单服务中,我们并没有实时去拉取商品服务中的价格,而是将订单创建时的价格保存下来。这样即使商品价格调整,也不会影响历史订单的准确性。
实践篇:关键代码与实现思路

下面我分享几个实际开发中常用的模式和技术实践。
服务调用:Feign + OpenFeign
为了简化服务间的调用,我们使用OpenFeign,并结合Ribbon做负载均衡。
@FeignClient(name = "product-service")
public interface ProductServiceClient {
@GetMapping("/products/{id}")
Product getProductById(@PathVariable("id") Long productId);
}
然后在OrderService里可以直接注入并调用:
@Service
public class OrderServiceImpl implements OrderService {
private final ProductServiceClient productServiceClient;
public OrderServiceImpl(ProductServiceClient client) {
this.productServiceClient = client;
}
public Order createOrder(Long productId, Integer quantity) {
Product product = productServiceClient.getProductById(productId);
// 创建订单逻辑...
}
}
当然,这只是一个简单示例,真实场景中我们还做了熔断降级处理(Hystrix)、请求链路追踪(SkyWalking)等。
异步解耦:RocketMQ
为了避免服务之间因为调用失败而互相拖垮,我们引入了RocketMQ进行事件驱动。
例如,当订单创建成功后,发送一个事件通知库存服务:
rocketMQTemplate.convertAndSend("ORDER_CREATED_TOPIC", orderEvent);
库存服务监听该Topic,执行库存扣减:
@RocketMQMessageListener(topic = "ORDER_CREATED_TOPIC", consumerGroup = "inventory-group")
public class InventoryConsumer implements RocketMQListener<OrderCreatedEvent> {
private final InventoryService inventoryService;
public void onMessage(OrderCreatedEvent event) {
inventoryService.reduceStock(event.getProductId(), event.getQuantity());
}
}
这里要注意幂等性和重试策略,比如我们会在消息中加入唯一ID,消费方根据ID判断是否已处理过。
分布式事务:Seata的使用体验
Seata是我们后期引入的重要中间件之一,主要用于订单创建+库存更新这种场景。
在订单服务的入口方法上添加注解:
@GlobalTransactional
public void createOrderWithInventoryDeduct(...) {
// 创建订单
orderDao.save(order);
// 调用库存服务扣减库存
inventoryService.deduct(productId, quantity);
}
但在实践中,我们发现:
- 性能损耗较大,特别是在并发高的时候;
- 需要保证各服务都接入Seata客户端,增加了部署复杂度;
- 并不是所有场景都需要全局事务,有些可以用最终一致性方案替代。
所以,我们并没有在所有业务中强制使用Seata,而是在特定高一致性要求的场景下谨慎启用。
开发踩坑记录:那些你可能会遇到的问题
服务注册发现不稳定
早期我们采用Eureka,后来换成Nacos,是因为:
- Eureka在节点宕机时反应较慢,存在注册延迟;
- Nacos支持动态配置推送、健康检查更强,且能配合Sentinel做限流熔断。
建议:如果你要做大规模微服务部署,Nacos是个更优选择。
接口版本控制没做好,线上炸锅
有一次我们在ProductService里升级了个接口,新增了一个字段,结果OrderService不知道这个变化,直接报错崩溃。痛定思痛,我们做了两件事:
- 接口版本控制:使用URL Path版本号,例如
/v1/products/{id}; - 接口兼容性测试:上线前自动运行MockServer模拟不同版本的服务行为。
日志分散,排查困难
初期每个服务的日志打本地文件,出了问题要一台台机器上去查,效率低下。后来我们搭建了ELK(Elasticsearch + Logstash + Kibana),配合Logback远程写入,终于实现了集中化日志管理。
另外推荐一个好用的工具:SkyWalking,可以实现链路追踪和分布式监控,大大提升了排障效率。
成果与收获:重构后的变化
经过5个月的努力,我们的重构终于初见成效:
- 发布节奏快了近一倍,每个服务独立部署,互不影响;
- 系统稳定性提升,故障范围可控;
- 数据库压力降低,分库分表之后QPS提高了30%以上;
- 新人上手更快,因为服务边界明确、职责单一;
- 技术债务降低,老系统中的脏活臭活被重新设计清理了一遍。
当然,也不是全部都是正面收益,比如运维成本确实上升了,需要更多的自动化监控和报警机制。
给读者的建议:我的几点经验总结

如果你也在考虑从单体向微服务演进,或者正在转型过程中,以下是我亲身经历过的一些经验和建议:
✅ 1. 不要为了微服务而微服务
很多人看到大家都在用微服务,就觉得自己也必须拆。其实要看业务规模和团队能力。微服务不是银弹,它解决了一部分问题,也带来了更多复杂度。
建议:先从小范围试点,再逐步推广。
✅ 2. 提前做好基础设施建设
别等到服务拆完了才发现:
- 没有链路追踪;
- 没有日志聚合;
- 没有健康检查;
- 没有容错机制。
这些基础设施必须和微服务一起规划。否则你会陷入“刚拆完就修基建”的窘境。
建议:优先构建DevOps体系,再考虑服务拆分。
✅ 3. 数据拆分比服务拆分更难
服务拆得好,数据拆不好,一切白搭。尤其是历史数据迁移、旧系统兼容等问题,往往是重构中最耗时的部分。
建议:早动手梳理数据模型,制定详细迁移计划。
✅ 4. 接口设计要严谨,别让别人骂你
服务间通信靠接口,如果接口改一次,其他人都跟着受累。要像对待对外API一样对待你的服务接口。
建议:接口要有版本控制,变更需走评审流程。
✅ 5. 保持团队认知一致
微服务不是某一个开发的事,而是整个技术团队的事。你要确保每个人都了解架构的变化方向,知道新规范,不然就会出现有人还在往旧库里写数据的情况。
建议:组织定期培训、代码Review、架构决策文档公开透明。
尾声:技术之外的一点感悟
回顾这一整年的重构之路,最深的感受就是——技术从来都不是最难的,人的问题才是重点。
我们经历了架构上的阵痛期、组织上的磨合期、团队沟通的挑战期。每一次争吵背后,都是认知差异;每一个深夜加班,都是技术债的利息。
但我依然相信,只要方向正确,坚持下去,终有回报。
最后想送给所有正在转型路上的你一句话:
“不是所有改变都会立刻见效,但不迈出第一步,永远都不会变。”
愿我们在各自的技术之路上越走越远,共勉!

评论 0