application.yml 示例

罗秀珍△
2025-06-29 10:24
阅读 408

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

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

记得那是我参与过的一个电商项目,初期我们团队只有五个人,业务模块不多,代码也还算可控。那时候用的是典型的 Spring Boot 单体架构,前后端分离,部署也很简单,一个 jar 包扔上服务器就搞定了。

但随着用户增长、业务扩展,系统越来越复杂,问题也接踵而至:

  • 功能耦合严重,改一个小功能都得动整个工程;
  • 发布频繁出错,上线就像走钢丝;
  • 性能瓶颈越来越明显,数据库压力剧增;
  • 不同的业务线开始争夺开发资源,协作效率直线下降。

很明显,单体架构已经撑不住了。我们必须做架构拆分,而最合适的选择就是 微服务

这篇文章我想用自己的亲身经历告诉你,为什么我们要从单体走向分布式?这个过程中我们遇到了哪些真实的问题?又是怎么一步步解决的?


项目背景和痛点分析

我们的项目是一个电商交易平台,主要包括用户中心、商品中心、订单中心、支付中心和营销中心这几个核心模块。刚开始的时候,这些模块都是写在一个项目里,共用一个数据库。

随着功能越来越多,每次上线都要全量打包发布,稍微有点改动就得重启整个应用。有一次在发版本时不小心把线上数据库连接配置弄错了,直接导致整个平台停摆一小时——这可真是一记“大锤”!

更糟糕的是,各个功能之间的依赖关系越来越复杂,比如:

  • 修改用户头像,可能会影响订单页面显示;
  • 添加一个优惠券规则,结果影响了下单流程;
  • 某个模块出了性能问题,整个系统都跟着卡顿。

这些问题归根结底就在于 模块间高度耦合、维护成本高、扩展性差

所以最终我们决定:是时候重构架构,迈向微服务了!


微服务拆分方案制定

一开始我们想当然地以为,只要把原来的模块独立出来变成一个个服务就可以了。后来才发现,事情远没有那么简单。

首先我们面临的问题是:

  • 模块之间如何解耦?
  • 数据库要不要拆分?
  • 各个服务之间如何通信?
  • 如何处理跨服务事务?
  • 原有接口要怎么兼容?
  • 服务治理怎么做?

为了确保拆分顺利,我们制定了以下几个基本原则:

  1. 按业务边界划分服务
    我们将原先的大模块根据业务职责划分成独立的服务,比如用户服务、订单服务、商品服务、库存服务等。

  2. 各自独立数据库,避免共享表
    每个服务都有自己专属的数据源,不再共享同一个数据库,减少数据层面的耦合。

  3. 接口采用 REST + JSON 通信
    初期我们选择了最简单的 HTTP 调用方式(后面也会引入 RPC),统一以 JSON 作为数据交换格式。

  4. 逐步拆分,保持兼容性
    由于无法一蹴而就,我们先从非核心模块入手,逐步剥离原有功能,同时保证老接口依然可用。

  5. 引入注册中心和服务发现机制
    使用 Eureka 做服务注册与发现,方便服务之间查找彼此并调用。

  6. 日志集中化和链路追踪
    引入 ELK 和 Zipkin 来监控日志和服务调用链,帮助排查问题。


技术选型与实现思路

经过调研和讨论,我们确定了以下技术栈:

  • Spring Cloud Alibaba:作为微服务框架基础
  • Nacos:代替 Eureka,提供注册中心 + 配置中心能力
  • Feign + Ribbon:用于服务间通信
  • Sentinel:做流量控制和熔断降级
  • Seata:处理分布式事务
  • MySQL 分库分表 + ShardingSphere
  • RocketMQ:异步消息队列,做事件驱动
  • Redis Cluster:缓存支撑
  • ELK + Zipkin:日志收集 + 分布式链路追踪
  • Docker + Jenkins + Harbor + K8s:持续集成部署

微服务架构示意图-1

下面简单介绍一下几个关键组件的落地过程。


关键代码片段和配置示例

服务注册与发现(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

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