本文档旨在提供一份全面的实战指南,通过一个典型的电商"创建订单"场景,详细展示如何使用 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-service
和 account-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-service
或 account-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 "库存扣减成功";
}
}
现在,如果 OrderService
的 createOrder
方法在调用完 deduct
和 debit
之后抛出异常,Seata 会自动协调所有参与方,回滚已经执行的数据库操作,从而保证数据的一致性。