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

Rerank观察员
2025-06-26 13:35
阅读 509

引言:为什么我会踏上微服务的转型之路?

引言:为什么我会踏上微服务的转型之路?

还记得2019年那会儿,我们公司的一个核心系统还是一套庞大的Java单体应用。这个系统承载着整个公司的核心业务流程,包括订单、库存、支付等等。最初它运行得还不错,但随着功能越来越多、团队规模越来越大,维护起来越来越吃力。

代码库像滚雪球一样膨胀,每次上线都要小心翼翼地做回归测试,生怕改动一个地方就牵一发动全身。更头疼的是,新功能开发周期越来越长,一个小需求可能要在多个模块之间来回修改,牵扯到好几个小组之间的协作。

我们尝试过模块化拆分,把不同业务划成不同的子模块,但本质上还是跑在一个JVM里,部署还是单一节点。这种“伪模块化”并没有真正解决问题,反而让依赖管理变得更加复杂。

当时我就意识到,如果不做出改变,这棵大树迟早会被自己的枝叶压垮。

于是,在一次技术评审会上,我提出了一个大胆的想法:“要不咱们试试微服务?”


项目背景:从单体走向分布式的真实动机

项目背景:从单体走向分布式的真实动机

这套系统是为一家中型连锁零售企业打造的后台管理系统,覆盖线上商城、门店POS系统、供应链、仓储等模块。系统用户量不算大,日活跃大概在几千人左右,但每个请求都涉及到多个业务逻辑操作,且对稳定性要求非常高。

当时的痛点主要包括:

  • 部署难:任何小改动都需要整个系统重启
  • 扩展性差:某个模块出现性能瓶颈时无法单独扩容
  • 开发效率低:多人协作导致频繁的代码冲突和版本问题
  • 故障隔离差:一处出错整个系统瘫痪

这些问题累积下来,最终促使我们在2020年初启动了微服务架构的重构计划。


遇到的挑战:现实远比设想复杂得多

遇到的挑战:现实远比设想复杂得多

虽然网上有很多微服务的成功案例,但真到了自己动手的时候才发现,理论和实践之间的差距不是一点点。

第一个坑:拆分边界怎么定?

刚开始我们很迷茫,到底该怎么拆?是按业务来分,比如订单服务、用户服务、库存服务?还是按照层级来划分,比如DAO层、业务层、API层?

这个问题讨论了很久,最后我们参考了DDD(领域驱动设计)的思想,以业务能力为中心进行划分。例如:

  • 订单相关功能统一归为订单服务
  • 用户信息、权限、登录注册统一归为用户服务
  • 库存查询、出入库操作归为库存服务

但在初期实践中,我们发现有些业务边界并不清晰。比如订单中涉及库存的操作应该如何处理?是调用库存服务API,还是直接访问数据库?

我们尝试了一段时间跨服务调用,结果发现响应时间变慢很多。后来决定采用“数据冗余+异步同步”的方式,既保证响应速度,又避免强耦合。

第二个坑:服务间通信怎么做?

一开始我们选择了Spring Cloud Feign作为服务间通信的方式,简单方便,和Eureka集成也很顺畅。

然而不久后遇到了两个大问题:

  1. Feign客户端配置不当导致请求超时
  2. 服务雪崩效应

第一个问题是由于Feign默认的超时时间太短(连接1s,读取1s),而我们的某些服务在高峰期响应本身就比较慢,导致大量请求失败。

解决办法是在application.yml中加上如下配置:

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000

第二个问题则出现在某个服务出现延迟或宕机时,请求堆积在网关,造成连锁反应。我们引入了Hystrix作为熔断器,但后来发现它的线程池模型在高并发下表现不佳,最终换成了Resilience4j,轻量级、响应式更好。

第三个坑:数据一致性如何保障?

微服务的最大挑战之一就是分布式事务的问题。比如下单场景,需要同时扣减库存、生成订单、扣除用户积分等多个操作。

我们尝试过几种方案:

  • 本地事务 + 最终一致性:先保证本地事务成功,再通过MQ异步通知其他服务执行后续操作
  • TCC补偿事务:定义Try、Confirm、Cancel三个阶段,适合金融类强一致场景
  • Saga模式:适用于链路较长的流程型业务,出错时通过逆向操作回退

最终我们选择了前两种结合使用。对于高并发下的非关键路径使用消息队列异步处理,而对于资金相关的操作则采用TCC,确保最终一致性。


技术选型与架构设计

为了支撑微服务系统的顺利运行,我们需要搭建一套完整的基础设施。我们的技术栈主要包括:

组件 技术选型
注册中心 Nacos
网关 Spring Cloud Gateway
服务通信 OpenFeign + Resilience4j
配置中心 Nacos
分布式事务 Seata
数据库 MySQL + MyCat(分库分表)
日志聚合 ELK
监控告警 Prometheus + Grafana + SkyWalking

下面是整体架构图的一部分(文字描述):

外部请求 -> API Gateway -> 各个微服务(订单、用户、库存)
                             ↓
                   调用其他服务 或 操作数据库
                             ↓
                    异步通知 via RocketMQ

网关的设计考虑

我们最初使用Zuul作为网关,但在实际使用中发现性能不够好,尤其是在高并发情况下延迟较高。后来切换到Gateway之后,性能提升明显。

一些关键配置示例:

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**

网关层面我们也做了限流、鉴权、日志追踪等功能,避免重复工作分散到各个服务中去。


关键代码片段分享

服务间通信示例(Feign Client)

@FeignClient(name = "inventory-service", path = "/api/inventory")
public interface InventoryServiceClient {

    @PostMapping("/deduct")
    ResponseDTO deductStock(@RequestParam("productId") Long productId,
                            @RequestParam("quantity") Integer quantity);
}

这里我们封装了对库存服务的调用,并配合Resilience4j做降级:

@Bean
public InventoryServiceClient fallbackInventoryClient() {
    return (productId, quantity) -> new ResponseDTO(false, "服务暂不可用,请稍后再试");
}

分布式事务 TCC 示例

以库存服务为例,定义TCC接口:

public interface InventoryTccAction {

    @TwoPhaseBusinessAction(name = "deductStock")
    boolean prepare(BusinessActionContext ctx, @ActionContextParam("productId") Long productId,
                    @ActionContextParam("quantity") Integer quantity);

    @Commit
    boolean commit(BusinessActionContext ctx);

    @Rollback
    boolean rollback(BusinessActionContext ctx);
}

实现部分会在prepare阶段检查并锁定库存,在commit时正式扣除,在rollback时释放锁定数量。


生产踩坑经验总结

1. 服务注册与发现不稳定

早期我们使用Eureka作为注册中心,但在实际生产中遇到过服务实例未及时下线的问题,导致调用失败。

解决方案是我们转用了Nacos,它不仅支持服务注册与发现,还能作为配置中心,同时提供了健康检查机制,实时性更强。

2. 跨域问题层出不穷

在网关和服务之间转发请求时,我们曾因CORS设置不规范而导致前端请求被浏览器拦截。

我们在网关层统一设置了全局CORS策略:

@Configuration
public class CorsConfig {

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange exchange, WebFilterChain chain) -> {
            ServerHttpResponse response = exchange.getResponse();
            ServerHttpRequest request = exchange.getRequest();

            if (request.getHeaders().containsKey(HttpHeaders.ORIGIN)) {
                response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
                response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
                response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization");
                response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
            }
            return chain.filter(exchange);
        };
    }
}

3. 日志追踪混乱

多个服务产生的日志混在一起,排查问题非常困难。我们引入了SkyWalking来做全链路追踪,为每个请求分配唯一traceId,并将日志输出到ELK。

4. 数据库压力过大

最初我们把所有服务共用一个MySQL数据库,结果很快出现锁竞争严重、QPS下降等问题。

后来我们做了三件事:

  • 每个服务独立数据库
  • 使用MyCat做垂直拆分
  • 热点数据引入Redis缓存

效果立竿见影,数据库响应时间从平均80ms降到15ms以内。


效果总结:改造后的收益与提升

经历了大半年的微服务重构和优化,我们取得了以下成果:

指标 改造前 改造后
部署频率 2周/次 每天可多次发布
服务可用性 98% 99.8%
新功能上线时间 平均2周 平均3天
单机QPS ~200 ~1500
故障影响范围 全系统 仅受影响服务

最重要的是,团队协作更加顺畅了,前后端分离更明确,测试和运维也能针对性地管理。


我的经验建议:给正在转型的你一点启发

如果你也正准备或正在进行微服务架构的升级,我可以分享几个关键建议:

1. 不要盲目追求“微”

并不是服务越多越好。合理的拆分粒度非常重要,建议优先按业务边界来划分,而不是为了拆而拆。初期可以先拆成3~5个核心服务,后续逐步细化。

2. 基础设施先行

在开始拆服务之前,一定要先准备好基础平台:

  • 服务注册与发现(Nacos / Consul)
  • 网关(Spring Cloud Gateway / Zuul)
  • 配置中心(Nacos / Spring Cloud Config)
  • 日志监控(ELK + SkyWalking)
  • 分布式事务组件(Seata / TCC)

没有这些底座支持,服务多了只会带来更大的混乱。

3. 接口设计比代码更重要

服务间的通信必须要有良好的契约设计。推荐使用OpenAPI标准,提前做好文档说明和版本控制。不要等到上线前才临时改接口。

4. 重视异常处理和熔断机制

微服务天然存在网络不可靠的问题,所以必须处理好超时、重试、熔断、降级等机制。否则一个小故障可能拖垮整条调用链。

5. 数据治理不能忽视

微服务意味着数据库拆分,这时候更要考虑数据一致性、迁移、备份、容灾等问题。建议尽早规划主从复制、异地多活等方案。


写在最后:这场转型带来的不仅仅是技术变化

对我来说,这次微服务的落地不仅仅是一次技术升级,更是一次思维的转变。它让我重新理解了系统设计的初衷——不是为了炫技,而是为了让系统更好地服务于业务,服务于团队。

记得有一次凌晨值班,有个服务突然出现OOM(Out of Memory),我第一反应是去查堆栈、看GC日志、调整JVM参数……折腾了好几个小时才定位到是一个定时任务内存泄漏。那时候我才明白,分布式的世界,运维的压力同样巨大。

但正是这一次次的“坑”,一次次的“救火”,让我和团队一步步成长。如今回头看,那段日子虽然辛苦,却也无比充实。

希望这篇文章能给你一点启发。如果你正在做类似的事情,别怕困难,坚持走下去。因为每一步微服务的改进,都在让系统变得更健壮、更灵活、更有生命力。


如果你觉得这篇文章有帮助,欢迎留言交流,或关注我的技术博客。让我们一起在微服务的道路上越走越远!

评论 0

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