Spring Cloud Alibaba 微服务实战指南

本文档旨在提供一份全面的实战指南,通过一个典型的电商"创建订单"场景,详细展示如何使用 Spring Cloud Alibaba 的核心组件构建、保护和管理微服务。所有代码示例都已整合,方便您直接参考和使用。

场景设定:电商下单

我们将模拟一个包含以下微服务的简化电商系统:

  • order-service: 订单服务,作为事务发起者,负责创建订单并调用其他服务。
  • stock-service: 库存服务,负责扣减指定商品的库存。
  • account-service: 账户服务,负责扣减用户账户的余额。

这个场景将贯穿所有组件的演示,包括服务发现 (Nacos)、远程调用 (OpenFeign)、熔断降级 (Sentinel) 和分布式事务 (Seata)。

组件关系图

scss 复制代码
下游服务
Spring Cloud Alibaba 基础设施
业务微服务 (Transaction Group)
客户端请求
1. 创建订单 (本地DB)
2. Feign调用
3. Feign调用
服务注册/发现
配置管理
服务注册/发现
配置管理
服务注册/发现
配置管理
流量/熔断规则
流量/熔断规则
流量/熔断规则
事务协调
事务协调
事务协调
扣减库存 (本地DB)
扣减余额 (本地DB)
Stock DB
Account DB
Nacos
Sentinel
Seata
order-service
Order DB
stock-service
account-service
Spring Cloud Gateway
API Request

第一步:项目基础依赖 (父 pom.xml)

一个健壮的项目始于统一的依赖管理。在多模块项目的父 pom.xml 文件中,我们使用 <dependencyManagement> 来锁定所有 Spring Cloud Alibaba 组件、Spring Cloud 和 Spring Boot 的版本,避免版本冲突。

xml 复制代码
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
</properties>
​
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

第二步:Nacos - 服务注册与配置中心

Nacos 是微服务架构的基石,承担着"服务注册与发现中心"和"配置中心"的双重角色。所有微服务都需要依赖它。

2.1 微服务依赖 (order-service/pom.xml)

每个微服务模块都需要引入 Nacos 的客户端依赖。

xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2.2 微服务配置 (bootstrap.yml)

连接 Nacos 的配置必须写在 bootstrap.yml (或 bootstrap.properties) 文件中,因为它需要在应用上下文加载之前,优先从配置中心拉取配置。

yaml 复制代码
# Spring Boot 应用配置
spring:
  application:
    # 应用名称,这是服务注册到 Nacos 的唯一标识
    name: order-service
  cloud:
    nacos:
      # Nacos Server 的地址
      server-addr: 127.0.0.1:8848
      # 配置中心的相关配置
      config:
        # 配置文件的数据格式
        file-extension: yml
        # 指定 Nacos 命名空间,用于环境隔离 (dev, test, prod)
        namespace: public
        # 指定配置分组
        group: DEFAULT_GROUP
        # 共享配置,可以被多个服务共用,例如数据库连接、Redis配置等
        shared-configs:
          - data-id: common.yml # 共享配置的 Data ID
            refresh: true # 是否开启动态刷新
      # 服务发现的相关配置
      discovery:
        namespace: public
        group: DEFAULT_GROUP

2.3 启用服务发现 (主启动类)

最后,在应用的主启动类上使用 @EnableDiscoveryClient 注解来激活服务发现功能。

typescript 复制代码
package com.example.orderservice;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
​
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 为第三步提前开启 Feign 功能
public class OrderServiceApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

第三步:OpenFeign - 声明式服务调用

OpenFeign 让微服务之间的调用变得像调用本地方法一样简单。order-service 将使用它来调用 stock-serviceaccount-service

3.1 Feign 依赖 (order-service/pom.xml)

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3.2 定义 Feign 客户端接口

order-service 中,我们为需要调用的每个服务创建一个接口。

调用库存服务:

less 复制代码
package com.example.orderservice.feign;
​
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
​
// value = "stock-service" 指明了要调用的服务在 Nacos 中的名称
@FeignClient(value = "stock-service")
public interface StockFeignClient {
​
    @PostMapping("/stock/deduct")
    String deduct(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

调用账户服务:

less 复制代码
package com.example.orderservice.feign;
​
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
​
@FeignClient(value = "account-service")
public interface AccountFeignClient {
​
    @PostMapping("/account/debit")
    String debit(@RequestParam("userId") Long userId, @RequestParam("amount") Double amount);
}

请确保主启动类已添加 @EnableFeignClients 注解 (见 2.3 节)。

3.3 在业务逻辑中使用 Feign

现在可以在 OrderService 中注入并使用这些客户端了。

kotlin 复制代码
package com.example.orderservice.service;

import com.example.orderservice.feign.AccountFeignClient;
import com.example.orderservice.feign.StockFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private StockFeignClient stockFeignClient;
    
    @Autowired
    private AccountFeignClient accountFeignClient;

    public void createOrder(Long userId, Long productId, Integer count, Double amount) {
        System.out.println("---[订单服务]---:开始创建订单...");

        // 1. 调用库存服务,扣减库存
        System.out.println("---[订单服务]---:调用库存服务,进行库存扣减...");
        String stockResult = stockFeignClient.deduct(productId, count);
        System.out.println("---[订单服务]---:库存服务调用结果: " + stockResult);

        // 2. 调用账户服务,扣减余额
        System.out.println("---[订单服务]---:调用账户服务,进行余额扣减...");
        String accountResult = accountFeignClient.debit(userId, amount);
        System.out.println("---[订单服务]---:账户服务调用结果: " + accountResult);
        
        // 3. 此处省略创建订单记录到数据库的业务逻辑
        System.out.println("---[订单服务]---:订单记录创建成功!");
    }
}

第四步:Sentinel - 流量卫兵与熔断器

为了防止因某个下游服务(如 stock-service)的故障而导致 order-service 也崩溃(即雪崩效应),我们使用 Sentinel 进行熔断降级。

4.1 Sentinel 依赖 (order-service/pom.xml)

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

4.2 Sentinel 配置 (application.yml)

连接 Sentinel 控制台并启用 Feign 的 Sentinel 支持。

yaml 复制代码
spring:
  cloud:
    sentinel:
      # Sentinel Dashboard 的地址和端口
      transport:
        dashboard: localhost:8080
      # 默认情况下,Sentinel 会在第一次调用时才与 Dashboard 建立连接
      # 设置为 true,可以在项目启动时就立即连接
      eager: true

# Feign 配置
feign:
  sentinel:
    # 开启 Feign 对 Sentinel 的全面支持
    enabled: true

4.3 创建 Feign 的 Fallback 降级逻辑

我们需要创建一个类,实现 Feign 客户端接口。当远程调用失败或触发熔断时,程序会执行这个类中的同名方法。

kotlin 复制代码
package com.example.orderservice.feign.fallback;

import com.example.orderservice.feign.StockFeignClient;
import org.springframework.stereotype.Component;

/**
 * 库存服务 Feign 客户端的降级处理逻辑
 * 当 StockFeignClient 的方法调用失败时,会执行这里的对应方法
 */
@Component
public class StockFeignFallback implements StockFeignClient {

    @Override
    public String deduct(Long productId, Integer count) {
        // 服务熔断后,我们不抛出异常,而是返回一个预设的、友好的结果。
        // 这可以让主业务流程继续下去,或者进行其他补偿操作。
        System.err.println("!!! 调用库存服务失败,StockFeignClient 已熔断!");
        return "fallback: stock service is unavailable.";
    }
}

4.4 为 Feign 客户端指定降级类

修改 StockFeignClient 接口,通过 fallback 属性指定刚才创建的降级处理类。

less 复制代码
package com.example.orderservice.feign;

import com.example.orderservice.feign.fallback.StockFeignFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

// 使用 fallback 属性指定降级类
// 当 stock-service 不可用或调用超时,将自动调用 StockFeignFallback 中的方法
@FeignClient(value = "stock-service", fallback = StockFeignFallback.class)
public interface StockFeignClient {

    @PostMapping("/stock/deduct")
    String deduct(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

第五步:Seata - 分布式事务保障

创建订单、扣减库存、扣减余额,这三个操作必须全部成功或全部失败。Seata AT 模式是实现这一目标的高效方案。

5.1 Seata 依赖 (所有参与者)

order-service, stock-service, account-service 都需要添加 Seata 依赖。

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

5.2 Seata 配置 (application.yml)

所有参与事务的服务都需要配置它们的事务组。

yaml 复制代码
spring:
  cloud:
    alibaba:
      seata:
        # 定义事务组名称。这个名称必须与你的 Seata Server 中 VGROUP_MAPPING 配置里的值一致。
        # 格式通常是: [服务名]-tx-group
        tx-service-group: order_tx_group

5.3 事务发起方 (OrderService)

在事务的起点------order-service 的业务方法上,添加 @GlobalTransactional 注解。

kotlin 复制代码
package com.example.orderservice.service;

import com.example.orderservice.feign.AccountFeignClient;
import com.example.orderservice.feign.StockFeignClient;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private StockFeignClient stockFeignClient;
    
    @Autowired
    private AccountFeignClient accountFeignClient;

    // @Autowired
    // private OrderDao orderDao; // 假设用于操作订单表的 DAO

    /**
     * 使用 Seata 的 @GlobalTransactional 注解开启全局事务
     * name 属性可以自定义,便于问题追踪
     * rollbackFor = Exception.class 表示任何异常都会触发回滚
     */
    @GlobalTransactional(name = "my-create-order-tx", rollbackFor = Exception.class)
    @Transactional // 本地事务注解同样需要,保证本地数据库操作的原子性
    public void createOrder(Long userId, Long productId, Integer count, Double amount) {
        System.out.println("-------> 全局事务 XID 开始");

        // 1. 创建订单(本地数据库操作)
        System.out.println("-------> [order-service] 创建订单记录...");
        // orderDao.create(order);
        
        // 2. 远程调用库存服务,扣减库存
        System.out.println("-------> [order-service] 调用库存服务...");
        stockFeignClient.deduct(productId, count);

        // 3. 远程调用账户服务,扣减余额
        System.out.println("-------> [order-service] 调用账户服务...");
        accountFeignClient.debit(userId, amount);

        // 4. 模拟一个业务异常,测试全局回滚
        if (amount > 1000) {
           throw new RuntimeException("订单金额过大,模拟异常,触发全局事务回滚!");
        }

        System.out.println("-------> 全局事务正常提交");
    }
}

5.4 事务参与方 (StockController & AccountController)

对于事务的参与方,在 AT 模式下,代码是无侵入的。你 不需要stock-serviceaccount-service 的业务方法上添加任何 Seata 注解。Seata 会自动通过代理的数据源 (DataSource) 将这些服务中的数据库操作纳入全局事务管理。

Stock-Service Controller 示例:

less 复制代码
package com.example.stockservice.controller;

// ... imports

@RestController
@RequestMapping("/stock")
public class StockController {

    // @Autowired
    // private StockService stockService; // 注入操作数据库的 Service

    @PostMapping("/deduct")
    public String deduct(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) {
        System.out.println("---> [stock-service] 收到扣减库存请求, 商品ID: " + productId + ", 数量: " + count);
        // stockService.deduct(productId, count); // 这里是实际的数据库 UPDATE 操作
        System.out.println("---> [stock-service] 库存扣减成功!");
        return "库存扣减成功";
    }
}

现在,如果 OrderServicecreateOrder 方法在调用完 deductdebit 之后抛出异常,Seata 会自动协调所有参与方,回滚已经执行的数据库操作,从而保证数据的一致性。

相关推荐
考虑考虑30 分钟前
Springboot3.4.x中的@Bean使用
spring boot·后端·spring
努力的小雨30 分钟前
AI编程实战:云开发疯狂助攻,React + Vite 做出 FPS 网页游戏不是梦
后端
君爱学习2 小时前
RocketMQ延迟消息是如何实现的?
后端
Falling422 小时前
使用 CNB 构建并部署maven项目
后端
程序员小假2 小时前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文2 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
萧曵 丶2 小时前
Rust 中的返回类型
开发语言·后端·rust
高兴达3 小时前
Spring boot入门工程
java·spring boot·后端
到账一个亿5 小时前
后端树形结构
后端