Skip to content

微服务架构详解

一、为什么需要微服务?

1.1 单体架构的困境

在互联网早期,大多数应用采用单体架构(Monolithic Architecture):所有功能模块打包在一个 war/jar 包中,部署在同一台服务器上。这种架构在项目初期开发效率高、部署简单,但随着业务增长,问题逐渐暴露:

+----------------------------------------------------------+
|                    单体应用 (Monolith)                     |
|  +----------------+  +----------------+  +----------------+ |
|  |   用户模块      |  |   订单模块      |  |   商品模块      | |
|  +----------------+  +----------------+  +----------------+ |
|  |   支付模块      |  |   物流模块      |  |   库存模块      | |
|  +----------------+  +----------------+  +----------------+ |
|                      单一数据库                              |
+----------------------------------------------------------+

单体架构的痛点:

痛点描述
部署耦合改一行代码需要整个应用重新部署,部署窗口大,风险高
扩展困难无法针对热点模块单独扩容,只能整体扩容,资源浪费
技术锁定整个应用必须使用同一种技术栈,无法灵活选择
团队协作冲突多人修改同一代码库,合并冲突频繁
可靠性差一个模块的 bug(如内存泄漏)可能拖垮整个应用
认知负担代码量膨胀后,新人难以理解全貌

1.2 微服务的核心思想

微服务架构(Microservices Architecture)由 Martin Fowler 和 James Lewis 于 2014 年正式定义:将单一应用划分为一组小服务,每个服务运行在独立进程中,服务间通过轻量级通信机制协作,每个服务围绕业务能力构建,可独立部署。

+----------+     +----------+     +----------+     +----------+
| 用户服务  |     | 订单服务  |     | 商品服务  |     | 支付服务  |
| (独立DB)  |     | (独立DB)  |     | (独立DB)  |     | (独立DB)  |
+----------+     +----------+     +----------+     +----------+
      |                |               |                |
      +----------------+---------------+---------------+ 
                   服务注册中心 / API 网关

微服务 vs 单体架构对比:

维度单体架构微服务架构
部署整体打包部署每个服务独立部署
扩展整体水平扩展按需对特定服务扩展
技术栈单一技术栈每个服务可选择不同技术栈
团队组织按技术分层(前端/后端/DBA)按业务领域(跨职能团队)
数据管理统一数据库每个服务有自己的数据库
故障隔离一处故障可能全局崩溃故障隔离在单个服务内
测试复杂度高(需要集成测试)
运维复杂度高(需要容器化、编排、监控)

1.3 微服务的核心价值

价值说明
独立部署单个服务修改后无需部署整个应用,部署频率从月级提升到日级甚至小时级
技术多样性不同服务可以根据场景选择最适合的技术(如推荐系统用 Python,核心交易用 Java)
团队自治每个小团队(2-pizza team)独立负责一个或多个服务,减少跨团队协调
故障隔离服务 A 宕机不应影响服务 B,通过熔断、降级等机制保护系统
可扩展性针对热点服务(如秒杀订单服务)独立扩容,资源利用率更高
可维护性代码库更小,新人上手更快,理解成本更低

二、微服务拆分原则

2.1 DDD 限界上下文

DDD(Domain-Driven Design,领域驱动设计)中的**限界上下文(Bounded Context)**是微服务拆分的核心指导思想。一个限界上下文对应一个微服务,上下文内部有统一的业务模型和语言(Ubiquitous Language)。

电商系统限界上下文划分示例:

+------------------+    +------------------+    +------------------+
|   用户上下文       |    |   订单上下文       |    |   商品上下文       |
|  (User Service)   |    |  (Order Service)  |    | (Product Service) |
|                  |    |                  |    |                  |
|  用户、地址、积分  |    |  订单、订单项、   |    |  商品、SKU、分类、 |
|                  |    |  订单状态         |    |  库存             |
+------------------+    +------------------+    +------------------+
        |                       |                       |
        v                       v                       v
+------------------+    +------------------+    +------------------+
|   支付上下文       |    |   物流上下文       |    |   营销上下文       |
| (Payment Service) |    | (Logistics Svc)   |    | (Marketing Svc)  |
|                  |    |                  |    |                  |
|  支付单、退款、   |    |  运单、物流轨迹、  |    |  优惠券、活动、   |
|  对账             |    |  仓库             |    |  秒杀             |
+------------------+    +------------------+    +------------------+

2.2 拆分原则

原则说明反例
高内聚低耦合相关行为聚集在一起,服务间依赖最小化把订单和支付拆成同一个服务(耦合过高)
按业务能力拆分围绕业务领域而非技术层次拆分按 Controller/Service/Dao 分层拆分微服务
单一职责每个服务只负责一个业务领域一个服务既管用户又管订单又管物流
数据自治每个服务拥有自己的数据库,通过 API 访问数据多个服务直接连接同一个数据库
渐进式拆分从边缘业务开始拆分,逐步演进一次性全面拆分,风险极高

2.3 拆分时机

不要过早拆分! 微服务不是银弹,它带来了分布式系统的复杂性。以下信号表明可以考虑拆分:

  • 团队规模超过 20 人,代码合并冲突频繁
  • 不同模块的变更速率差异大
  • 某些模块需要独立扩缩容
  • 不同模块需要不同的技术栈
  • 模块边界清晰,业务理解成熟

三、服务间通信

3.1 通信方式对比

维度同步通信(REST/gRPC)异步通信(消息队列)
耦合度高(调用方等待响应)低(生产者和消费者解耦)
实时性高(即时响应)较低(最终一致)
可靠性低(依赖被调用方可用)高(消息持久化,可重试)
适用场景查询、实时操作事件通知、数据同步、削峰
代表技术REST、gRPC、DubboKafka、RocketMQ、RabbitMQ

3.2 RESTful API(同步)

最常用的同步通信方式,基于 HTTP 协议,简单通用。

java
// 订单服务调用用户服务获取用户信息
@RestController
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/orders/{orderId}")
    public OrderDetailVO getOrderDetail(@PathVariable Long orderId) {
        // 1. 查询订单
        Order order = orderService.getById(orderId);
        
        // 2. 通过 REST 调用用户服务获取用户信息
        String userServiceUrl = "http://user-service/users/{userId}";
        UserVO user = restTemplate.getForObject(userServiceUrl, UserVO.class, order.getUserId());
        
        // 3. 通过 REST 调用商品服务获取商品信息
        String productServiceUrl = "http://product-service/products/{productId}";
        ProductVO product = restTemplate.getForObject(productServiceUrl, ProductVO.class, order.getProductId());
        
        return OrderDetailVO.builder()
                .order(order)
                .user(user)
                .product(product)
                .build();
    }
}

3.3 gRPC(同步)

基于 HTTP/2 + Protobuf,高性能,适合内部服务间通信。

java
// gRPC 服务端定义 (proto 文件)
// service OrderService {
//   rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
// }

// 服务端实现
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
    
    @Override
    public void getOrder(GetOrderRequest request, 
                         StreamObserver<GetOrderResponse> responseObserver) {
        Order order = orderService.getById(request.getOrderId());
        GetOrderResponse response = GetOrderResponse.newBuilder()
                .setOrderId(order.getId())
                .setAmount(order.getAmount().doubleValue())
                .setStatus(order.getStatus())
                .build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

// 客户端调用
@GrpcClient("order-service")
private OrderServiceGrpc.OrderServiceBlockingStub orderStub;

public void queryOrder() {
    GetOrderResponse response = orderStub.getOrder(
        GetOrderRequest.newBuilder().setOrderId(1001L).build()
    );
}

3.4 消息队列(异步)

java
// 订单服务:创建订单后发送消息
@Service
public class OrderService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        // 1. 保存订单
        Order order = orderDao.save(buildOrder(request));
        
        // 2. 发送订单创建事件(异步通知其他服务)
        OrderCreatedEvent event = new OrderCreatedEvent();
        event.setOrderId(order.getId());
        event.setUserId(order.getUserId());
        event.setAmount(order.getAmount());
        rocketMQTemplate.convertAndSend("order-created-topic", event);
        
        return order;
    }
}

// 库存服务:消费订单创建事件,扣减库存
@Component
@RocketMQMessageListener(topic = "order-created-topic", consumerGroup = "inventory-consumer")
public class OrderCreatedConsumer implements RocketMQListener<OrderCreatedEvent> {
    
    @Override
    public void onMessage(OrderCreatedEvent event) {
        // 扣减库存
        inventoryService.deductStock(event.getProductId(), event.getQuantity());
    }
}

四、微服务核心挑战与解决方案

4.1 服务发现

问题: 服务实例动态变化(扩缩容、重启),调用方如何知道目标服务在哪里?

解决方案: 服务注册中心(Service Registry)

+----------+    注册     +----------------+    发现     +----------+
| 服务提供者 | ---------> |  注册中心       | ---------> | 服务消费者 |
| (Provider) | <--------- |  (Nacos/Eureka) | <--------- | (Consumer) |
+----------+    心跳     +----------------+    订阅     +----------+

Spring Cloud + Nacos 实现:

java
// 服务提供者:注册到 Nacos
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

// application.yml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: order-service

// 服务消费者:通过服务名调用
@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    @LoadBalanced  // 客户端负载均衡
    public UserVO getUser(Long userId) {
        // 直接使用服务名,Ribbon/LoadBalancer 自动负载均衡
        return restTemplate.getForObject(
            "http://user-service/users/" + userId, UserVO.class
        );
    }
}

4.2 配置管理

问题: 微服务数量多,配置分散,修改配置需要重启服务。

解决方案: 配置中心(Nacos Config / Apollo)

java
// Spring Cloud + Nacos 配置中心
@RestController
@RefreshScope  // 支持配置热更新
public class ConfigController {
    
    @Value("${order.timeout:30}")
    private int orderTimeout;  // 可在 Nacos 控制台动态修改,无需重启
    
    @Value("${feature.new-checkout:false}")
    private boolean newCheckoutEnabled;  // 功能开关(动态灰度)
    
    @GetMapping("/config")
    public Map<String, Object> getConfig() {
        return Map.of("orderTimeout", orderTimeout, 
                      "newCheckout", newCheckoutEnabled);
    }
}

// bootstrap.yml
spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        shared-configs:
          - data-id: common-config.yaml
            group: DEFAULT_GROUP
            refresh: true

4.3 服务容错

问题: 一个服务故障可能级联导致整个系统崩溃(雪崩效应)。

解决方案: Sentinel / Resilience4j 实现熔断、降级、限流

java
// Sentinel 熔断降级
@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    // 使用 Sentinel 注解实现熔断
    @SentinelResource(
        value = "getUserInfo",
        fallback = "getUserFallback",        // 降级方法
        blockHandler = "getUserBlockHandler"  // 限流处理
    )
    public UserVO getUserInfo(Long userId) {
        return restTemplate.getForObject(
            "http://user-service/users/" + userId, UserVO.class
        );
    }
    
    // 降级方法:当用户服务不可用时返回默认值
    public UserVO getUserFallback(Long userId, Throwable e) {
        log.error("获取用户信息失败,userId={}", userId, e);
        return UserVO.builder()
                .userId(userId)
                .nickname("匿名用户")
                .build();
    }
    
    // 限流处理方法
    public UserVO getUserBlockHandler(Long userId, BlockException e) {
        log.warn("获取用户信息被限流,userId={}", userId);
        return UserVO.builder()
                .userId(userId)
                .nickname("系统繁忙,请稍后重试")
                .build();
    }
}

三种容错模式:

模式描述比喻
熔断(Circuit Breaker)故障率达到阈值后自动切断调用,快速失败电闸跳闸
降级(Fallback)服务不可用时返回兜底数据备用方案
限流(Rate Limiting)控制请求速率,保护服务不被冲垮排队限流

4.4 分布式事务

问题: 一个业务操作跨多个服务,如何保证数据一致性?

解决方案: 最终一致性 > 强一致性,常用 Seata(AT/TCC/Saga 模式)

java
// Seata AT 模式:对业务无侵入,自动回滚
@Service
public class OrderService {
    
    @Autowired
    private AccountServiceClient accountClient;
    @Autowired
    private InventoryServiceClient inventoryClient;
    
    @GlobalTransactional  // 一键开启分布式事务
    @Transactional
    public void createOrder(CreateOrderRequest request) {
        // 1. 创建订单(本地事务)
        orderDao.save(buildOrder(request));
        
        // 2. 扣减库存(远程调用)
        inventoryClient.deduct(request.getProductId(), request.getQuantity());
        
        // 3. 扣减余额(远程调用)
        accountClient.debit(request.getUserId(), request.getAmount());
        
        // 任何一步失败,Seata 自动回滚所有操作
    }
}

4.5 API 网关

问题: 客户端需要知道所有服务地址,认证、限流等逻辑散落在各服务中。

解决方案: Spring Cloud Gateway

java
// Spring Cloud Gateway 路由配置
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

// 自定义全局过滤器:认证鉴权
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
}

4.6 可观测性

问题: 一个请求跨多个服务,如何追踪调用链、定位问题?

解决方案: 日志收集(ELK)+ 链路追踪(SkyWalking / Zipkin)+ 指标监控(Prometheus + Grafana)

+--------+     +--------+     +--------+
| 服务 A  | --> | 服务 B  | --> | 服务 C  |
+--------+     +--------+     +--------+
     |             |              |
     +-------------+--------------+ 
                   |
                   v
         +------------------+
         |  SkyWalking 链路追踪 |
         |  全链路拓扑 + 性能分析 |
         +------------------+
java
// 使用 Micrometer + Prometheus 暴露指标
@RestController
public class MetricsController {
    
    private final MeterRegistry meterRegistry;
    private final Counter orderCounter;
    
    public MetricsController(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        // 自定义业务指标
        this.orderCounter = Counter.builder("orders.created.total")
                .description("Total number of orders created")
                .register(meterRegistry);
    }
    
    @PostMapping("/orders")
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        Order order = orderService.create(request);
        orderCounter.increment();  // 打点
        return order;
    }
}

五、Spring Cloud 生态全景

组件功能推荐方案替代方案
服务注册与发现服务实例管理NacosEureka, Consul, Zookeeper
配置中心动态配置管理NacosApollo, Spring Cloud Config
API 网关统一入口,路由转发Spring Cloud GatewayZuul, Kong
服务调用RPC 通信OpenFeign + LoadBalancerDubbo, gRPC
熔断降级服务容错SentinelResilience4j, Hystrix(已停更)
分布式事务跨服务事务Seata基于 MQ 的最终一致性
链路追踪调用链分析SkyWalkingZipkin, Jaeger
消息驱动异步通信Spring Cloud Stream直接使用 RocketMQ / Kafka

六、CAP 定理在微服务中的应用

CAP 定理:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)中的两个。

            C (一致性)
           /\
          /  \
         /    \
        /  CA  \
       /--------\
      /          \
     /    CP      \     AP
    /--------------\----------
   P                A
  (分区容错性)      (可用性)

在微服务中,P(分区容错)是必须选择的(网络分区不可避免),所以实际是在 CP 和 AP 之间选择:

选择含义典型场景技术选型
CP发生分区时,优先保证一致性,牺牲可用性金融交易、账户余额Zookeeper, Etcd
AP发生分区时,优先保证可用性,牺牲强一致性社交动态、商品浏览Eureka, Nacos

微服务中的实践: 大多数业务场景选择 AP(最终一致性),通过消息队列、补偿机制保证数据最终一致。对于核心金融场景,使用 CP 方案或 TCC 分布式事务。

七、微服务代码示例:完整电商微服务

7.1 项目结构

ecommerce-platform/
├── ecommerce-gateway/          # API 网关
├── ecommerce-common/           # 公共模块
├── user-service/               # 用户服务
│   ├── user-service-api/       # Feign 接口定义
│   └── user-service-impl/      # 接口实现
├── order-service/              # 订单服务
├── product-service/            # 商品服务
└── inventory-service/          # 库存服务

7.2 OpenFeign 声明式调用

java
// 定义 Feign 接口(放在 user-service-api 模块中,供其他服务引用)
@FeignClient(name = "user-service", path = "/users",
             fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
    
    @GetMapping("/{userId}")
    Result<UserVO> getUserById(@PathVariable("userId") Long userId);
    
    @GetMapping("/batch")
    Result<List<UserVO>> getUsersByIds(@RequestParam("ids") List<Long> ids);
}

// 降级工厂:获取异常信息
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable cause) {
        log.error("UserClient 调用失败", cause);
        return new UserClient() {
            @Override
            public Result<UserVO> getUserById(Long userId) {
                return Result.fail("用户服务不可用");
            }
            @Override
            public Result<List<UserVO>> getUsersByIds(List<Long> ids) {
                return Result.fail("用户服务不可用");
            }
        };
    }
}

// 订单服务中使用 Feign 调用
@Service
public class OrderService {
    
    @Autowired
    private UserClient userClient;  // 像调用本地方法一样调用远程服务
    
    public OrderDetailVO getOrderDetail(Long orderId) {
        Order order = orderDao.getById(orderId);
        Result<UserVO> userResult = userClient.getUserById(order.getUserId());
        return OrderDetailVO.builder()
                .order(order)
                .user(userResult.getData())
                .build();
    }
}

八、面试高频问题

Q1: 微服务和单体架构各有什么优缺点?什么时候应该选择微服务?

  • 单体:开发简单、部署简单、调试方便,适合小型项目和早期阶段
  • 微服务:独立部署、技术异构、故障隔离,适合大型项目和多团队协作
  • 选择标准:团队规模 > 20 人,业务复杂度高,有独立部署需求时考虑微服务

Q2: 如何确定微服务的拆分粒度?

  • 以 DDD 限界上下文为参考,一个上下文一个服务
  • 遵循"两个人原则":一个服务足够小,可以让两个开发人员完全理解
  • 从粗粒度开始,逐步细化,避免过度拆分

Q3: 微服务间如何保证数据一致性?

  • 尽量避免分布式事务,通过业务设计规避
  • 对于必须跨服务的数据一致性,使用最终一致性方案:
    • 消息队列 + 本地消息表
    • Seata(AT/TCC/Saga 模式)
    • 补偿机制(对账、定时任务修复)

Q4: 如何解决微服务调用链过长的问题?

  • 减少服务嵌套,合理划分服务粒度
  • 使用异步消息解耦,减少同步调用链
  • 数据冗余(如订单服务冗余用户昵称、商品名称)
  • 服务合并(过度拆分的服务合并回去)

Q5: 服务注册中心的 CAP 如何选择?

  • Nacos:默认 AP,支持切换到 CP(通过 Distro 协议和 Raft 协议)
  • Eureka:AP,优先保证可用性
  • Zookeeper:CP,Leader 选举期间不可用
  • 推荐:微服务注册中心选 AP(可用性优先),配置中心选 CP(一致性优先)

Q6: 如何实现服务平滑上下线?

  • 上线:先注册但不接收流量 → 预热 → 逐步放开流量
  • 下线:先从注册中心摘除 → 等待 in-flight 请求处理完 → 优雅关闭(Spring Boot Graceful Shutdown)
  • 配置示例:server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s

Q7: 分布式链路追踪的原理是什么?

一个请求进入系统后,生成全局唯一的 TraceId。每个服务处理时生成自己的 SpanId,并将 TraceId 透传到下游服务。所有 Span 上报到追踪系统,形成完整的调用链。关键组件:TraceId(全局跟踪)、SpanId(单个服务调用)、ParentSpanId(父子关系)。