application.yml 示例
微服务架构设计实战:从单体到分布式

记得那是我参与过的一个电商项目,初期我们团队只有五个人,业务模块不多,代码也还算可控。那时候用的是典型的 Spring Boot 单体架构,前后端分离,部署也很简单,一个 jar 包扔上服务器就搞定了。
但随着用户增长、业务扩展,系统越来越复杂,问题也接踵而至:
- 功能耦合严重,改一个小功能都得动整个工程;
- 发布频繁出错,上线就像走钢丝;
- 性能瓶颈越来越明显,数据库压力剧增;
- 不同的业务线开始争夺开发资源,协作效率直线下降。
很明显,单体架构已经撑不住了。我们必须做架构拆分,而最合适的选择就是 微服务。
这篇文章我想用自己的亲身经历告诉你,为什么我们要从单体走向分布式?这个过程中我们遇到了哪些真实的问题?又是怎么一步步解决的?
项目背景和痛点分析
我们的项目是一个电商交易平台,主要包括用户中心、商品中心、订单中心、支付中心和营销中心这几个核心模块。刚开始的时候,这些模块都是写在一个项目里,共用一个数据库。
随着功能越来越多,每次上线都要全量打包发布,稍微有点改动就得重启整个应用。有一次在发版本时不小心把线上数据库连接配置弄错了,直接导致整个平台停摆一小时——这可真是一记“大锤”!
更糟糕的是,各个功能之间的依赖关系越来越复杂,比如:
- 修改用户头像,可能会影响订单页面显示;
- 添加一个优惠券规则,结果影响了下单流程;
- 某个模块出了性能问题,整个系统都跟着卡顿。
这些问题归根结底就在于 模块间高度耦合、维护成本高、扩展性差。
所以最终我们决定:是时候重构架构,迈向微服务了!
微服务拆分方案制定
一开始我们想当然地以为,只要把原来的模块独立出来变成一个个服务就可以了。后来才发现,事情远没有那么简单。
首先我们面临的问题是:
- 模块之间如何解耦?
- 数据库要不要拆分?
- 各个服务之间如何通信?
- 如何处理跨服务事务?
- 原有接口要怎么兼容?
- 服务治理怎么做?
为了确保拆分顺利,我们制定了以下几个基本原则:
按业务边界划分服务
我们将原先的大模块根据业务职责划分成独立的服务,比如用户服务、订单服务、商品服务、库存服务等。各自独立数据库,避免共享表
每个服务都有自己专属的数据源,不再共享同一个数据库,减少数据层面的耦合。接口采用 REST + JSON 通信
初期我们选择了最简单的 HTTP 调用方式(后面也会引入 RPC),统一以 JSON 作为数据交换格式。逐步拆分,保持兼容性
由于无法一蹴而就,我们先从非核心模块入手,逐步剥离原有功能,同时保证老接口依然可用。引入注册中心和服务发现机制
使用 Eureka 做服务注册与发现,方便服务之间查找彼此并调用。日志集中化和链路追踪
引入 ELK 和 Zipkin 来监控日志和服务调用链,帮助排查问题。
技术选型与实现思路
经过调研和讨论,我们确定了以下技术栈:
- Spring Cloud Alibaba:作为微服务框架基础
- Nacos:代替 Eureka,提供注册中心 + 配置中心能力
- Feign + Ribbon:用于服务间通信
- Sentinel:做流量控制和熔断降级
- Seata:处理分布式事务
- MySQL 分库分表 + ShardingSphere
- RocketMQ:异步消息队列,做事件驱动
- Redis Cluster:缓存支撑
- ELK + Zipkin:日志收集 + 分布式链路追踪
- Docker + Jenkins + Harbor + K8s:持续集成部署

下面简单介绍一下几个关键组件的落地过程。
关键代码片段和配置示例
服务注册与发现(Nacos)
server:
port: 8080
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos 地址
主类加上启动注解即可自动注册:
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
服务调用(Feign Client)
@FeignClient(name = "order-service") // 根据服务名调用
public interface OrderServiceClient {
@GetMapping("/orders/{userId}")
List<Order> getOrdersByUserId(@PathVariable String userId);
}
这样就能通过 Feign 发起远程调用,配合 Ribbon 实现客户端负载均衡。
分布式事务(Seata)
我们在下单场景中需要同时操作用户账户余额和订单状态,使用 Seata 确保原子性。
@GlobalTransactional
public void placeOrder(String userId, String productId, int quantity) {
// 扣减库存
inventoryService.decreaseStock(productId, quantity);
// 创建订单
orderRepository.save(new Order(userId, productId, quantity));
// 扣除用户余额
userService.deductBalance(userId, amount);
}
只需加一个 @GlobalTransactional 注解,就可以开启全局事务管理。
实战中踩过的坑和经验总结
微服务听起来很美,但在实际落地过程中,我们真的是踩了不少坑,有些甚至差点翻车。
1. 接口版本兼容性没做好,导致服务崩溃
早期我们对服务版本管理不够重视,一次升级后订单服务的返回结构变了,用户服务调用时报错一堆异常,导致线上订单创建失败。
教训:必须对接口做版本管理,建议使用路径或 Header 控制版本号:
@GetMapping("/v2/orders/{userId}")
或者使用请求头:
GET /orders HTTP/1.1
Accept: application/vnd.myapp.v2+json
2. 没有做好限流降级,导致雪崩效应
高峰期订单服务突然挂掉,导致所有依赖它的服务都开始报错,整个系统瘫痪。
解决方案:接入 Sentinel,在调用关键服务前添加熔断逻辑:
@SentinelResource(value = "getOrders", fallback = "fallback_getOrders")
public List<Order> getOrdersByUserId(String userId) {
return orderService.getOrdersByUserId(userId);
}
private List<Order> fallback_getOrders(String userId, Throwable t) {
return Collections.emptyList(); // 失败返回空列表
}
3. 日志分散难定位问题
微服务拆开后,每个服务单独输出日志,排查起来特别麻烦。我们最初只能去服务器上 grep log,效率极低。
解决方案:搭建 ELK(Elasticsearch + Logstash + Kibana)统一日志收集,并结合 Zipkin 进行链路追踪。
4. 数据同步慢,出现一致性问题
初期我们用了定时任务来做数据同步,比如库存扣减之后,订单状态更新延迟了几秒,用户刷新一下又看到还能买,结果超卖了。
后来改成 RocketMQ 异步通知机制:
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendStockChangeMessage(String productId, int changeAmount) {
rocketMQTemplate.convertAndSend("STOCK_CHANGE_TOPIC", new StockChangeEvent(productId, changeAmount));
}
消费者监听消息,完成后续业务逻辑。
架构调整后的效果和收益
拆完微服务之后,整个系统变化非常显著:
- 上线频率提高:原来每周最多上线两次,现在每天都能灰度发布;
- 故障范围变小:一个服务挂了不影响整体,隔离性强;
- 运维更清晰:服务拓扑图一目了然,监控报警粒度细化;
- 弹性扩缩容:热点服务可以独立扩容,而不是整台机器堆资源;
- 团队协作更顺畅:不同小组各司其职,互不干扰;
- 性能提升:数据库压力分布开来,读写分离做得更好。
更重要的是,我们建立了一套完整的微服务治理体系,包括注册发现、配置中心、限流降级、链路追踪、日志聚合、CI/CD 流程等等,这为后续其他项目的微服务建设打下了坚实基础。
给读者的一些建议和注意事项
如果你也在考虑从单体架构转向微服务,这里是我一路走来的一些真诚建议:
1. 不要盲目拆服务,业务先行
微服务不是越多越好,重点在于是否真正形成了业务闭环。服务切分太细,反而会带来额外的维护成本。
2. 数据模型提前规划
数据库拆分比代码拆分还重要,服务内部封装数据访问层,外部只能通过接口获取数据,防止数据透传。
3. 别忘了可观测性
日志、指标、链路三要素缺一不可。否则你永远不知道到底哪个环节出问题了。
4. 异步解耦很重要
用消息队列解除长链路依赖,降低服务间调用压力。特别是涉及到多个服务协同的场景,异步更安全可靠。
5. 自动化程度越高越好
CI/CD、健康检查、自动伸缩、监控报警……这些都是微服务时代必备的基础设施。
6. 团队协作要跟上
单体时代一个人可以搞定一切,但微服务下每个人负责一个服务,沟通、文档、测试都变得尤为重要。
7. 保留回滚能力
任何变更都要留有退路。即使服务拆开了,也要能快速切换回旧模式,避免长时间宕机。
写在最后:我的一点感悟
回头看看,从单体到微服务其实是一次“割裂式的成长”。前期阵痛难免,但一旦体系建立起来,就会发现整个系统变得更灵活、更健壮、更容易扩展。
我曾经以为微服务是个很高大上的东西,后来才发现它本质还是解决人和代码的组织问题。架构从来都不是用来炫技的,而是为了更好的协作、更高的交付效率和更强的稳定性。
希望这篇文章能帮你在转型微服务的路上少走些弯路。如果有具体问题,欢迎留言交流,我也乐意继续分享我在实战中遇到的更多故事和思考。
毕竟,真正的高手,都是在真实项目中摔打出来的。

评论 0