微服务架构设计实战:从单体到分布式

后端Cloud
2025-06-18 18:38
阅读 529

背景介绍:为什么我们要重构系统?

背景介绍:为什么我们要重构系统?

我所在的公司是一家做电商导购的互联网平台,业务已经稳定运行了几年。最开始,我们用的是一个传统的单体应用,部署在 Tomcat 上,前后端分离,数据库是 MySQL 单点主从架构。随着用户量的快速增长、功能模块不断膨胀,原本“稳如老狗”的系统开始变得脆弱——上线一次要提前好几个小时准备,接口性能下降明显,团队协作越来越难。

举个真实例子:有一次促销前的版本上线,我们误改了一个 Redis 的 key 名字,结果缓存穿透直接导致首页打不开,用户体验雪崩式下滑。事后复盘发现,整个系统的耦合度太高,修改一处可能影响全局,开发效率和稳定性都到了极限。

于是,我们决定:把原来的单体系统拆分成微服务架构。


问题描述:拆分过程中遇到的挑战

服务器部署方案-1

问题描述:拆分过程中遇到的挑战

挑战一:如何合理划分服务边界?

这是我们面临的第一个问题。微服务的核心思想之一就是“高内聚、低耦合”,但实际操作中,很多同事都陷入了“到底怎么拆才对?”的争论。

比如一开始,有人想把用户相关逻辑独立成 user-service,还有人想按业务线拆,比如优惠券是一个 service、订单是一个 service。但后来我们发现,这种纯粹按业务拆的方式并不适用于所有场景,有些业务模块之间依赖太强。

举个例子: 优惠券服务需要获取用户的积分状态,这时候就需要调用 user-service。但如果两者频繁互相调用,就会带来新的性能瓶颈和可用性风险。

挑战二:跨服务调用和通信问题

服务拆开之后,各 service 需要通信。我们最初采用的是 Restful + Feign Client 方案,在测试环境没有问题,但是生产环境并发上来后,Feign 出现超时、连接池耗尽的情况,尤其是在大促期间尤为明显。

同时,调用链拉长导致定位问题变难。某个服务慢了,你得一步步查日志,还要看 TraceID,非常痛苦。

挑战三:配置管理和注册中心的选择

我们在初期使用了 Zookeeper 做注册中心,但在高并发下,ZK 经常出现连接不稳定的问题,特别是在某些节点重启或网络抖动的时候,服务注册信息更新延迟严重,影响整体服务发现能力。

再加上配置分散管理,各个微服务本地维护 application.yml,一旦有配置变更,就得一个个改,运维压力很大。

挑战四:数据一致性与数据库拆分策略

这是个大坑。当我们尝试将原数据库按模块垂直拆分时,发现很多表存在跨库关联查询的需求。例如,一个商品详情页要展示库存、用户评价、优惠信息等,这些信息分布在不同的库里,怎么办?

我们一开始想着用 Join,但那是不可能的。只能通过接口调用,或者冗余字段。冗余又带来了数据同步问题。比如库存变化时,如何及时更新其他服务的数据?


解决方案:我们的技术选型和实现思路

解决方案:我们的技术选型和实现思路

服务拆分策略:以业务领域为主 + 限界上下文为辅

最后我们总结出了一套比较清晰的服务拆分方式:

  1. 核心业务模块优先拆分:用户、订单、支付、商品这些核心模块优先独立。
  2. 限界上下文(Bounded Context)指导设计:参考 DDD(领域驱动设计)中的概念,确保每个服务只关注自己的职责。
  3. 避免循环依赖:如果两个服务经常互相调用,说明它们可能属于同一上下文,应合并处理。

使用 Nacos 替代 Zookeeper 和 Spring Cloud Config

为了统一服务注册和配置管理,我们引入了 Alibaba 的 Nacos,效果非常好:

  • 服务注册/发现功能稳定可靠,支持健康检查;
  • 集中式配置管理,修改配置后可以自动刷新服务配置(无需重启);
  • 支持分环境、分组管理,适合多租户场景;
  • 结合 Spring Cloud Gateway 做统一网关入口。

接口通信选用 Dubbo + Protobuf 提升性能

为了避免 Feign 在高并发下的性能问题,我们将核心服务间的通信切换为 Dubbo,并使用 Protobuf 序列化协议:

<!-- pom.xml -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
</dependency>

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.19.4</version>
</dependency>

Dubbo 支持负载均衡、服务治理(如降级、熔断)、多协议适配等特性,非常适合中大型项目。

数据库拆分策略:读写分离 + 分库分表 + 最终一致性处理

我们针对数据库采用了以下几个措施:

  1. 读写分离:使用 MyCat 或 ShardingSphere,将读操作和写操作分开。
  2. 分库分表:将订单表按用户 ID 哈希分区,商品表按类目划分。
  3. 最终一致性:在订单和库存服务之间的操作采用消息队列(Kafka)进行异步通知,保证事务的一致性。

引入消息队列解耦关键路径

为了减少服务间强依赖,我们引入 Kafka 做事件驱动的消息中间件:

  • 订单创建成功后发送消息到 Kafka;
  • 库存服务消费该消息,执行库存扣减;
  • 如果失败,进入重试机制或人工补偿。

这大大提升了系统的伸缩性和容错能力。


关键代码片段分享

1. Dubbo 接口定义

// 用户服务接口
public interface UserService {
    User getUserById(Long userId);
}

// 实现类
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Long userId) {
        return userMapper.selectById(userId);
    }
}
# application.yml
dubbo:
  application:
    name: user-service
  registry:
    address: nacos://127.0.0.1:8848
  protocol:
    name: dubbo
    port: 20880

2. 消息发送者(Kafka)

@Component
public class InventoryProducer {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void sendOrderCreatedEvent(Order order) {
        String json = JSON.toJSONString(order);
        kafkaTemplate.send("order_created", json);
    }
}

3. 消费者处理库存更新

@Component
public class InventoryConsumer {

    @KafkaListener(topics = "order_created")
    public void consume(String message) {
        Order order = JSON.parseObject(message, Order.class);
        inventoryService.decreaseStock(order.getProductId(), order.getCount());
    }
}

踩过的坑与解决经验

坑一:Nacos 启动失败,找不到本地 IP

这个问题发生在集群部署阶段,某些服务器上 Nacos 总是启动不起来,查看日志提示无法解析本机 IP。最后发现是由于服务器有多网卡,而默认网卡被错误识别。

解决方案:

手动指定 prefer-host-mode 并配置正确的本地 IP:

-Dspring.cloud.nacos.discovery.ip=192.168.10.101

坑二:Kafka 消费积压

大促期间订单量激增,消费者处理不过来,导致消息堆积。我们做了以下优化:

  • 增加消费者实例数量;
  • 提升消费者并行线程数;
  • 对 Kafka 分区进行扩容;
  • 加监控告警(Prometheus + Grafana),实时追踪堆积情况。

坑三:分布式事务问题

虽然我们尽量通过异步解耦来避免两阶段提交,但有些场景确实需要事务一致性,比如退款流程。

我们用了 Seata 进行分布式事务管理,但初期因为锁竞争和 XID 传递问题频频失败。后来统一规范了调用链路,加入日志埋点、异常兜底策略才解决问题。


效果总结:微服务带来的收益

完成第一期微服务改造后,我们得到了以下几点明显的收益:

指标 单体时代 微服务架构后
单次发布耗时 40+分钟 缩短至5~10分钟
系统平均响应时间 ~800ms 下降至 ~300ms
接口可用率 98% 提升至 99.9%
开发协作效率 多人修改冲突频发 模块隔离,协同顺畅
故障隔离能力 一处故障全站受影响 单个服务崩溃不影响其他

更重要的是,运维同学表示终于不用盯着日志疯狂跳脚了!


我的几点建议和注意事项

如果你也正在考虑微服务架构迁移,这里有几点我个人的经验建议:

✅ 明确服务边界的划分原则

不要一开始就追求完美设计,先从小模块入手。随着业务发展再不断细化。最重要的是 保持服务自治、职责单一

✅ 做好基础组件的选型工作

像注册中心、配置中心、链路追踪、日志聚合这些工具一定要提前调研好,否则后续集成成本会非常高。

✅ 接口设计要有前瞻性

服务接口要尽量稳定,避免频繁改动。推荐使用 REST + Swagger 文档化,加上版本控制(如 /api/v1/user)。

✅ 别忘记监控和报警体系

微服务带来的复杂度远高于单体,必须有一套完整的监控系统,包括:

  • 接口调用链追踪(如 SkyWalking、Pinpoint)
  • 指标聚合(Prometheus + Grafana)
  • 日志集中采集(ELK)

✅ 数据库拆分要考虑周全

千万不要图快,贸然拆库导致后期难以回滚。建议先从逻辑层面抽象开始,再逐步物理拆分。


写在最后:关于架构演进的一些思考

从业这么多年,我一直坚信一句话:没有最好的架构,只有最合适的架构。

微服务不是银弹,它解决了复杂系统的扩展性问题,但也带来了额外的运维成本、沟通成本和技术债。我们不能盲目跟风,更不能为了拆分而拆分。而是要在业务增长和团队规模的双重推动下,做出渐进式的架构升级。

最后分享一个小插曲:有天晚上凌晨两点,线上有个服务突然报错,我迷迷糊糊爬起来一看,TraceID 显示调用链路上竟然有 7 个服务串联。那一刻,我深刻意识到:

微服务,是对开发者责任心的巨大考验。

因为每一个服务的背后,不只是 API,更是无数用户的体验。

愿我们都在这条架构进化之路上,越走越稳健。💪


如需获取文中提到的相关代码工程模板(Spring Boot + Dubbo + Nacos + Kafka),欢迎在评论区留言或者私信我,我可以打包一份 demo 工程供你学习研究 😊

评论 0

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