微服务常见组件详解
一、组件总览
1.1 微服务架构全景图
┌──────────────────┐
│ Nacos │
│ 注册中心+配置中心 │
└────────┬─────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Gateway │ │ User-Service │ │ Order-Service │
│ 网关 │─────────────▶│ 用户服务 │◀────── ───▶│ 订单服务 │
└────────┬─────────┘ OpenFeign └────────┬─────────┘ OpenFeign └────────┬─────────┘
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Sentinel │ │
│ │ 限流/熔断/降级 │ │
│ └──────────────────┘ │
│ │
└───────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────┐
│ Seata │
│ 分布式事务 │
└──────────────────┘
1.2 核心组件一览
| 组件 | 定位 | 核心作用 | 解决的问题 |
|---|---|---|---|
| Nacos | 注册中心 + 配置中心 | 服务注册发现、配置集中管理 | 服务地址动态变化、配置散落各处 |
| Gateway | 服务网关 | 统一入口、路由转发、认证鉴权 | 客户端需要知道每个服务地址 |
| OpenFeign | 服务调用 | 声明式HTTP客户端、负载均衡 | 手写HTTP调用代码繁琐 |
| Sentinel | 服务容错 | 限流、熔断、降级 | 服务雪崩、系统过载 |
| Seata | 分布式事务 | 跨服务事务一致性 | 分布式环境下数据不一致 |
1.3 请求处理流程
一个典型的微服务请求流程:
用户请求 ──▶ Gateway(网关)
│
├── 1. 认证鉴权(JWT校验)
├── 2. 从Nacos获取服务列表
├── 3. 路由转发到目标服务
│
▼
目标服务(如订单服务)
│
├── 4. Sentinel限流/熔断检查
├── 5. 执行业务逻辑
├── 6. OpenFeign调用其他服务(如用户服务、库存服务)
├── 7. Seata管理分布式事务
│
▼
返回响应
1.4 技术选型对比
| 功能 | Spring Cloud Netflix(停更) | Spring Cloud Alibaba(推荐) |
|---|---|---|
| 注册中心 | Eureka | Nacos |
| 配置中心 | Spring Cloud Config | Nacos |
| 服务调用 | Feign + Ribbon | OpenFeign + LoadBalancer |
| 服务网关 | Zuul | Gateway |
| 服务容错 | Hystrix | Sentinel |
| 分布式事务 | - | Seata |
二、Nacos 详解(注册中心 + 配置中心)
2.1 是什么
Nacos是阿里巴巴开源的服务发现、配置管理平台,名称来源于 Na ming and Co nfiguration Service。它同时充当注册中心和配置中心,是Spring Cloud Alibaba的核心组件。
2.2 核心功能
| 功能 | 说明 |
|---|---|
| 服务注册 | 服务启动时将自身信息注册到Nacos |
| 服务发现 | 消费者从Nacos获取服务列表 |
| 健康检查 | 心跳检测,自动剔除不健康实例 |
| 配置管理 | 集中管理配置,支持动态刷新 |
| 命名空间 | 环境隔离(dev/test/prod) |
2.3 数据模型详解
Nacos的数据模型采用三层结构:
┌───────────────────────────────────────────────────────────────┐
│ Nacos 数据模型 │
├───────────────────────────────────────────────────────────────┤
│ Namespace(命名空间) │
│ └── 用于环境隔离,如 dev / test / prod │
│ └── 默认命名空间为 public │
│ │
│ Group(分组) │
│ └── 用于业务隔离,如不同项目或模块 │
│ └── 默认分组为 DEFAULT_GROUP │
│ │
│ Service/DataId │
│ └── 服务名(注册中心)或配置ID(配置中心) │
└───────────────────────────────────────────────────────────────┘
层级关系:Namespace > Group > Service/DataId
配置DataId命名规则:
${spring.application.name}-${spring.profiles.active}.${file-extension}
示例:user-service-dev.yaml
2.4 服务注册与发现原理
┌─────────────┐ ① 注册(服务名、IP、端口) ┌─────────────┐
│ 服务提供者 │ ────────────────────────────────▶ │ Nacos │
│ Provider │ ◀──────────────────────────────── │ Server │
└─────────────┘ ② 心跳(每5秒) └─────────────┘
│
│ ③ 服务列表
▼
┌─────────────┐ ④ 拉取服务列表 + 订阅变更 ┌─────────────┐
│ 服务消费者 │ ◀───────────────────────────────── │ 本地缓存 │
│ Consumer │ └─────────────┘
└─────────────┘
心跳机制详解:
| 时间节点 | 状态变化 | 说明 |
|---|---|---|
| 0-5秒 | 健康 | 正常发送心跳 |
| 5-15秒 | 健康 | 未收到心跳,仍在容忍期 |
| 15-30秒 | 不健康 | 标记为不健康,不参与负载均衡 |
| >30秒 | 剔除 | 从注册表删除 |
2.5 临时实例 vs 持久实例
Nacos支持两种实例类型:
| 特性 | 临时实例(默认) | 持久实例 |
|---|---|---|
| 健康检查 | 客户端主动上报心跳 | 服务端主动探测 |
| 下线方式 | 心跳超时自动剔除 | 手动下线或探测失败 |
| 存储位置 | 内存 | 磁盘持久化 |
| 适用场景 | 微服务、K8s | 传统IP固定服务 |
| CAP模式 | AP(可用性优先) | CP(一致性优先) |
yaml
# 配置为持久实例
spring:
cloud:
nacos:
discovery:
ephemeral: false # false=持久实例,true=临时实例(默认)
2.6 配置中心原理详解
长轮询机制:
┌─────────────┐ ┌─────────────┐
│ 客户端 │ ── ① 长轮询请求(30秒超时)──▶ │ Nacos │
│ Client │ │ Server │
└─────────────┘ └─────────────┘
▲ │
│ │
│ ② 配置变更时立即返回 │
└────────────────────────────────────────────┘
③ 客户端拉取新配置,刷新 @RefreshScope Bean
长轮询工作流程:
- 客户端发起请求,携带当前配置的MD5值
- 服务端hold住请求,最多等待30秒
- 如果配置有变化,立即返回变化的DataId
- 如果无变化,30秒后返回空结果
- 客户端收到响应后,主动拉取最新配置
2.7 配置优先级与共享配置
配置优先级(从高到低):
1. 精确匹配: ${name}-${profile}.${ext} 如 user-service-dev.yaml
2. 同服务不同环境:${name}.${ext} 如 user-service.yaml
3. 扩展配置: extension-configs 自定义扩展配置
4. 共享配置: shared-configs 多服务共享配置
共享配置示例:
yaml
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
# 共享配置(所有服务公用)
shared-configs:
- data-id: common-redis.yaml
group: COMMON_GROUP
refresh: true # 是否动态刷新
- data-id: common-mysql.yaml
group: COMMON_GROUP
refresh: true
# 扩展配置(本服务专用)
extension-configs:
- data-id: user-special.yaml
group: DEFAULT_GROUP
refresh: true
2.8 核心配置详解
yaml
spring:
application:
name: user-service
profiles:
active: dev
cloud:
nacos:
# ========== 注册中心配置 ==========
discovery:
server-addr: localhost:8848 # Nacos服务地址
namespace: dev-namespace-id # 命名空间ID(不是名称)
group: DEFAULT_GROUP # 分组
cluster-name: SH # 集群名称(同集群优先调用)
ephemeral: true # 临时实例(默认true)
weight: 1 # 权重(负载均衡用)
metadata: # 元数据
version: v1.0
env: dev
# ========== 配置中心配置 ==========
config:
server-addr: localhost:8848
namespace: dev-namespace-id
group: DEFAULT_GROUP
file-extension: yaml # 配置文件后缀
refresh-enabled: true # 启用动态刷新
timeout: 5000 # 超时时间(ms)
2.9 配置动态刷新
方式一:@RefreshScope + @Value
java
@RefreshScope // 配置变更时,Bean会重新创建
@RestController
public class ConfigController {
@Value("${custom.config:default}")
private String config;
@GetMapping("/config")
public String getConfig() {
return config; // 配置变更后自动获取新值
}
}
方式二:@ConfigurationProperties(推荐)
java
@Data
@Component
@ConfigurationProperties(prefix = "custom")
public class CustomConfig {
private String name;
private Integer timeout;
private List<String> servers;
}
// 使用时直接注入,自动刷新
@Service
public class MyService {
@Autowired
private CustomConfig customConfig;
}
方式三:监听配置变更事件
java
@Component
public class NacosConfigListener {
@NacosConfigListener(dataId = "user-service-dev.yaml", groupId = "DEFAULT_GROUP")
public void onConfigChange(String newConfig) {
// 配置变更时触发
log.info("配置变更: {}", newConfig);
// 执行自定义逻辑,如重新加载缓存
}
}
2.10 Nacos集群架构
┌───────────────────────────────────────────────────────────────┐
│ Nacos 集群架构 │
├───────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Nacos-1 │◀─▶│ Nacos-2 │◀─▶│ Nacos-3 │ Raft协议同步 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ └─────────────┴─────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ MySQL │ 外部存储(生产环境推荐) │
│ └─────────────┘ │
│ │
└───────────────────────────────────────────────────────────────┘
集群要求:至少3个节点,保证Raft协议正常选主
三、Sentinel 详解(服务容错)
3.1 是什么
Sentinel是阿里巴巴开源的流量控制组件,以流量为切入点,提供流量控制、熔断降级、系统负载保护等功能,保护服务的稳定性。
3.2 核心功能一览
| 功能 | 说明 | 应用场景 |
|---|---|---|
| 流量控制 | 控制QPS或并发线程数 | 防止突发流量压垂服务 |
| 熔断降级 | 根据失败率/慢调用比例熔断 | 防止级联故障 |
| 系统保护 | 根据系统负载自适应限流 | 保护服务器不被压幊 |
| 热点参数 | 针对热点参数限流 | 秒杀商品ID限流 |
| 授权规则 | 黑白名单控制 | 控制调用来源 |
3.3 流量控制详解
3.3.1 限流类型
| 限流类型 | 说明 | 配置参数 |
|---|---|---|
| QPS限流 | 每秒请求数超过阈值则限流 | grade=1, count=100 |
| 并发线程数限流 | 并发线程数超过阈值则限流 | grade=0, count=10 |
3.3.2 流控效果(限流算法)
┌────────────────────────────────────────────────────────────┐
│ 1. 直接拒绝(默认) │
│ 超过阈值的请求直接拒绝,返回BlockException │
│ │
│ 请求 ──▶ [阈值检查] ──▶ 通过/拒绝 │
├────────────────────────────────────────────────────────────┤
│ 2. Warm Up(预热) │
│ 冷启动场景,阈值从低到高逐渐增加 │
│ 防止服务刚启动时被突发流量压垂 │
│ │
│ 初始阈值 = 阈值/3 │
│ 预热时长内逐渐升高到设定阈值 │
│ │
│ 阈值/3 ────▶ 逐渐升高 ────▶ 最终阈值 │
│ (冷启动) (预热时长内) (设定阈值) │
├────────────────────────────────────────────────────────────┤
│ 3. 排队等待(匀速排队) │
│ 基于漏桶算法,请求匀速通过 │
│ 适合秒杀场景,将突发流量平滑化 │
│ │
│ 请求 ──▶ [队列等待] ──▶ 匀速通过 │
│ (最大等待时长) │
└────────────────────────────────────────────────────────────┘
3.3.3 限流算法原理
滑动窗口算法(Sentinel默认):
时间线: |----|----|----|----|----|
t1 t2 t3 t4 t5
窗口样本: [ 窗口1 ] ← 统计t1-t3的请求数
[ 窗口2 ] ← 窗口滑动,统计t2-t4
[ 窗口3 ]← 统计t3-t5
特点:每个时间片独立计数,窗口滑动时累加计算
优点:更平滑,避免固定窗口的临界突发问题
令牌桶算法:
┌─────────┐
│ 令牌桶 │ ← 以固定速率放入令牌
│ ○○○○○ │
│ ○○○ │ ← 桶有容量上限(burstCapacity)
└───┬─────┘
│
▼
请求 ──▶ [获取令牌] ──▶ 有令牌则通过,无则拒绝
特点:允许一定程度的突发流量(令牌桶未满时可突发)
漏桶算法:
┌─────────┐
请求 ──▶│ 漏桶 │ ← 请求进入桶中等待
│ ▓▓▓▓▓ │
│ ▓▓▓▓▓ │ ← 桶满则溢出(拒绝)
└───┬─────┘
│ 匀速流出
▼
处理请求
特点:严格限制输出速率,无突发
适用:秒杀场景,将突发流量平滑化
3.4 熔断降级详解
3.4.1 三种熔断策略
| 策略 | 触发条件 | 配置参数 | 适用场景 |
|---|---|---|---|
| 慢调用比例 | 慢调用比例 > 阈值 | slowRatioThreshold=0.5, minRequestAmount=5 | 接口RT波动大 |
| 异常比例 | 异常比例 > 阈值 | errorRatio=0.5, minRequestAmount=5 | 服务不稳定 |
| 异常数 | 异常数 > 阈值 | exceptionCount=5, statIntervalMs=60000 | 简单场景 |
3.4.2 熔断状态流转
失败率/慢调用超阈值
┌──────────────────────────┐
│ ▼
┌───────────────┐ ┌───────────────┐
│ CLOSED │ │ OPEN │
│ 关闭态 │ │ 打开态 │
│ (正常放行请求) │ │ (直接拒绝请求) │
└───────────────┘ └───────────────┘
▲ │
│ │
│ 探测请求成功 │ 熔断时长结束
│ ▼
│ ┌───────────────┐
└─────────────────────────│ HALF_OPEN │
│ 半开态 │
│ (放行一个探测) │
└───────────────┘
│
│ 探测请求失败
▼
回到 OPEN
关键参数说明:
statIntervalMs:统计时长,默认1000msminRequestAmount:最小请求数,不足则不触发熔断slowRatioThreshold:慢调用比例阈值(0-1)maxSlowRt:慢调用RT阈值(ms)recoveryTimeoutMs:熔断持续时间(ms)
3.5 热点参数限流
针对某个特定参数值进行限流,如对某个热点商品ID限流。
java
// 注解方式
@SentinelResource(
value = "getProduct",
blockHandler = "getProductBlockHandler"
)
public Product getProduct(Long productId) {
return productService.getById(productId);
}
// 热点参数配置(通过控制台或API)
// 对第0个参数限流,默认阈值100 QPS
// 特殊值:productId=1001 限流10 QPS(秒杀商品)
场景示例:
普通商品请求:100 QPS
热门商品(id=1001)请求:10 QPS ← 限制秒杀商品的访问频率
3.6 系统保护规则
根据系统负载自适应限流,保护服务器不被压幊。
| 指标 | 说明 | 触发条件 |
|---|---|---|
| LOAD | 系统load1(Linux) | load1 > 阈值且并发 > 核数*2.5 |
| CPU使用率 | CPU使用百分比 | CPU > 阈值 |
| 平均RT | 入口平均响应时间 | RT > 阈值 |
| 并发线程数 | 入口并发线程数 | 线程数 > 阈值 |
| 入口QPS | 入口QPS | QPS > 阈值 |
3.7 代码示例详解
方式一:注解方式(推荐)
java
@SentinelResource(
value = "getUser", // 资源名
blockHandler = "getUserBlockHandler", // 限流/熔断时的处理
blockHandlerClass = UserBlockHandler.class, // 处理类(方法需static)
fallback = "getUserFallback", // 业务异常时的降级
fallbackClass = UserFallback.class, // 降级类
exceptionsToIgnore = {IllegalArgumentException.class} // 忽略的异常
)
public User getUser(Long id) {
return userService.getById(id);
}
// 限流/熔断处理方法(必须是static,参数一致+BlockException)
public class UserBlockHandler {
public static User getUserBlockHandler(Long id, BlockException ex) {
log.warn("用户{}:请求被限流", id);
return new User(-1L, "限流中,请稍后重试");
}
}
// 业务异常降级方法(必须是static,参数一致+Throwable)
public class UserFallback {
public static User getUserFallback(Long id, Throwable ex) {
log.error("用户{}查询异常: {}", id, ex.getMessage());
return new User(-1L, "服务异常,返回默认用户");
}
}
方式二:整合OpenFeign
java
// 1. 开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
// 2. 定义Feign客户端
@FeignClient(
name = "user-service",
fallbackFactory = UserClientFallbackFactory.class
)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
// 3. 实现FallbackFactory(可获取异常信息)
@Component
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
log.error("调用user-service失败: {}", cause.getMessage());
return new UserClient() {
@Override
public User getUser(Long id) {
return new User(-1L, "降级用户-查询失败");
}
@Override
public User createUser(User user) {
throw new RuntimeException("创建用户服务不可用");
}
};
}
}
3.8 规则持久化
默认规则存储在内存中,重启丢失。生产环境需要持久化到Nacos。
yaml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
# 配置规则持久化到Nacos
datasource:
# 流控规则
flow:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-flow-rules
group-id: SENTINEL_GROUP
data-type: json
rule-type: flow
# 熔断规则
degrade:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-degrade-rules
group-id: SENTINEL_GROUP
data-type: json
rule-type: degrade
Nacos中的规则配置示例:
json
[
{
"resource": "getUser",
"limitApp": "default",
"grade": 1,
"count": 100,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
3.9 核心配置汇总
yaml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # 控制台地址
port: 8719 # 与控制台通信端口
eager: true # 饥饿加载,启动时就连接控制台
web-context-unify: false # 关闭上下文整合,支持链路模式
filter:
enabled: true # 开启web过滤器
url-patterns: /** # 拦截路径
# Feign整合Sentinel
feign:
sentinel:
enabled: true
四、Gateway 详解(服务网关)
4.1 是什么
Spring Cloud Gateway是Spring官方推出的API网关,基于WebFlux实现,具有高性能、支持异步非阻塞的特点,是微服务架构的统一入口。
4.2 核心概念
┌─────────────────────────────────────┐
│ Gateway │
外部请求 ──────────────▶ │ ┌─────────┐ ┌─────────┐ │
│ │Predicate│ ─▶│ Filter │ │
│ │ 断言 │ │ 过滤器 │ │
│ └─────────┘ └─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ Route 路由 │ │
│ │ id + uri + predicates │ │
│ │ + filters │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────┘
│
▼
后端微服务
| 概念 | 说明 | 示例 |
|---|---|---|
| Route | 路由,网关基本构建块 | id, uri, predicates, filters |
| Predicate | 断言,匹配请求条件 | Path, Method, Header, Query |
| Filter | 过滤器,修改请求/响应 | AddHeader, StripPrefix, 限流 |
4.3 路由配置
yaml
spring:
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service # lb表示负载均衡
predicates:
- Path=/api/user/** # 路径匹配
filters:
- StripPrefix=1 # 去掉第一层路径 /api
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
- Method=GET,POST # 请求方法匹配
filters:
- AddRequestHeader=X-Source, gateway # 添加请求头
# 基于时间的路由(活动页面)
- id: activity-service
uri: lb://activity-service
predicates:
- Path=/activity/**
- After=2024-06-18T00:00:00.000+08:00 # 指定时间后生效
4.4 常用Predicate
| Predicate | 说明 | 示例 |
|---|---|---|
| Path | 路径匹配 | Path=/api/** |
| Method | 请求方法 | Method=GET,POST |
| Header | 请求头 | Header=X-Token, \d+ |
| Query | 请求参数 | Query=name, jack |
| Cookie | Cookie值 | Cookie=sessionId, abc |
| After/Before | 时间条件 | After=2024-01-01T00:00:00Z |
| Weight | 权重路由 | Weight=group1, 8 |
4.5 常用Filter
yaml
filters:
# 路径相关
- StripPrefix=1 # 去掉前缀
- PrefixPath=/api # 添加前缀
- RewritePath=/old/(?.*)/, /new/$\{segment} # 重写路径
# 请求头相关
- AddRequestHeader=X-Request-Id, 123
- RemoveRequestHeader=Cookie
- SetRequestHeader=Host, newhost.com
# 响应头相关
- AddResponseHeader=X-Response-Time, 100
- RemoveResponseHeader=Server
# 限流
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒放入令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}" # 限流维度
4.6 自定义全局过滤器
java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 1. 获取Token
String token = request.getHeaders().getFirst("Authorization");
// 2. 校验Token
if (token == null || !token.startsWith("Bearer ")) {
// 返回401
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 3. 解析Token,传递用户信息到下游
String userId = parseToken(token);
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
@Override
public int getOrder() {
return -100; // 数值越小,优先级越高
}
}
4.7 跨域配置
yaml
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
4.8 请求处理流程详解
┌─────────────────────────────────────────────────────────────────────────┐
│ Gateway 请求处理完整流程 │
└─────────────────────────────────────────────────────────────────────────┘
外部请求
│
▼
┌────────────────────┐
│ 1.HttpWebHandler │ 接收HTTP请求,转换为ServerWebExchange
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ 2.DispatcherHandler│ WebFlux入口,分发请求
└─────────┬──────────┘
│
▼
┌────────────────────────┐
│ 3.RoutePredicateHandler│ 路由断言匹配
│ - 遍历所有Route │
│ - 执行Predicate │
│ - 找到匹配的路由 │
└─────────┬──────────────┘
│
▼
┌──────────────────────┐
│ 4.FilteringWebHandler│ 过滤器链执行
│ ┌──────────────┐ │
│ │Global Filter │ │ 全局过滤器(鉴权、日志等)
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │Gateway Filter│ │ 网关过滤器(路由级别)
│ └──────────────┘ │
└─────────┬────────────┘
│
▼
┌─────────────────────┐
│5. NettyRoutingFilter│ 代理请求到下游服务
│ - 负载均衡选择实例│
│ - 建立连接发送请求│
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ 6. Response Filter │ 响应过滤器(逆序执行)
└─────────┬──────────┘
│
▼
返回响应
4.9 自定义路由过滤器(GatewayFilter)
java
// 自定义路由过滤器 - 记录请求耗时
@Component
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final String START_TIME = "startTime";
private static final Logger log = LoggerFactory.getLogger(RequestTimeGatewayFilterFactory.class);
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// pre过滤:记录开始时间
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// post过滤:计算耗时
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
String path = exchange.getRequest().getURI().getPath();
if (config.isEnabled()) {
log.info("[{}] {} 耗时: {}ms",
exchange.getRequest().getMethod(),
path,
duration);
}
// 添加响应头
exchange.getResponse().getHeaders().add("X-Response-Time", duration + "ms");
}
}));
};
}
@Data
public static class Config {
private boolean enabled = true;
}
}
// 配置使用
// routes:
// - id: user-service
// uri: lb://user-service
// predicates:
// - Path=/api/user/**
// filters:
// - RequestTime=true
4.10 限流配置详解(基于Redis)
java
// 1. 自定义限流Key解析器
@Configuration
public class RateLimiterConfig {
// 基于IP限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
// 基于用户ID限流
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
);
}
// 基于接口路径限流
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value()
);
}
}
yaml
# 限流配置详解
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: RequestRateLimiter
args:
# 令牌桶每秒填充速率(允许的稳定请求速率)
redis-rate-limiter.replenishRate: 10
# 令牌桶最大容量(允许的突发流量)
redis-rate-limiter.burstCapacity: 20
# 每个请求消耗的令牌数
redis-rate-limiter.requestedTokens: 1
# 限流Key解析器
key-resolver: "#{@ipKeyResolver}"
redis:
host: localhost
port: 6379
限流原理说明:
replenishRate=10:每秒往令牌桶放入10个令牌burstCapacity=20:桶最多容纳20个令牌- 正常情况:每秒最多处理10个请求
- 突发情况:瞬时可处理20个请求(消耗桶内存量)
4.11 灰度发布(金丝雀发布)
yaml
# 基于Header的灰度路由
spring:
cloud:
gateway:
routes:
# 灰度版本路由(优先级高)
- id: user-service-gray
uri: lb://user-service-gray
predicates:
- Path=/api/user/**
- Header=X-Gray, true # 灰度标识
order: 0 # 数字越小优先级越高
# 正式版本路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
order: 1
---
# 基于权重的灰度路由
spring:
cloud:
gateway:
routes:
# 90%流量到正式版本
- id: user-service-prod
uri: lb://user-service
predicates:
- Path=/api/user/**
- Weight=user-group, 90
# 10%流量到灰度版本
- id: user-service-gray
uri: lb://user-service-gray
predicates:
- Path=/api/user/**
- Weight=user-group, 10
4.12 动态路由(从Nacos加载)
java
@Component
@Slf4j
public class DynamicRouteLoader implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
// 监听Nacos配置变更
@NacosConfigListener(dataId = "gateway-routes", groupId = "GATEWAY_GROUP")
public void onRouteChange(String config) {
log.info("路由配置变更: {}", config);
List<RouteDefinition> routes = JSON.parseArray(config, RouteDefinition.class);
// 删除旧路由
// 添加新路由
routes.forEach(route -> {
routeDefinitionWriter.save(Mono.just(route)).subscribe();
});
// 发布刷新事件
publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
}
4.13 核心配置汇总
yaml
spring:
cloud:
gateway:
# 全局默认过滤器
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin
- AddResponseHeader=X-Gateway-Version, 1.0.0
# 服务发现路由(自动从注册中心创建路由)
discovery:
locator:
enabled: true # 开启服务发现路由
lower-case-service-id: true # 服务名小写
# 全局超时配置
httpclient:
connect-timeout: 5000 # 连接超时5秒
response-timeout: 10000 # 响应超时10秒
pool:
type: ELASTIC # 连接池类型
max-connections: 1000 # 最大连接数
acquire-timeout: 5000 # 获取连接超时
# 重试配置
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: Retry
args:
retries: 3 # 重试次数
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET,POST
backoff:
firstBackoff: 100ms # 首次重试间隔
maxBackoff: 500ms # 最大重试间隔
factor: 2 # 间隔倍数
五、OpenFeign 详解(声明式服务调用)
5.1 是什么
OpenFeign是Spring Cloud的声明式HTTP客户端,通过注解方式定义接口,自动生成HTTP调用代码,简化服务间调用。
5.2 工作原理
┌────────────────────────────────────────────────────────────────┐
│ OpenFeign 工作流程 │
├────────────────────────────────────────────────────────────────┤
│ 1. @FeignClient 标注接口 │
│ ↓ │
│ 2. 启动时扫描接口,生成动态代理 │
│ ↓ │
│ 3. 调用方法时,解析注解(@GetMapping、@PathVariable等) │
│ ↓ │
│ 4. 从注册中心获取服务实例列表 │
│ ↓ │
│ 5. 负载均衡选择实例 │
│ ↓ │
│ 6. 构造HTTP请求并发送 │
│ ↓ │
│ 7. 反序列化响应结果 │
└────────────────────────────────────────────────────────────────┘
5.3 基本使用
java
// 1. 启用Feign客户端
@SpringBootApplication
@EnableFeignClients
public class OrderApplication { }
// 2. 定义Feign接口
@FeignClient(name = "user-service") // 服务名
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
@GetMapping("/users")
List<User> listUsers(@RequestParam("name") String name);
}
// 3. 注入使用
@Service
public class OrderService {
@Autowired
private UserClient userClient;
public Order createOrder(Long userId) {
// 像调用本地方法一样调用远程服务
User user = userClient.getUser(userId);
// ...
}
}
5.4 核心配置
yaml
feign:
client:
config:
default: # 全局配置
connectTimeout: 5000 # 连接超时
readTimeout: 5000 # 读取超时
loggerLevel: BASIC # 日志级别
user-service: # 针对特定服务配置
connectTimeout: 3000
readTimeout: 3000
# 开启压缩
compression:
request:
enabled: true
mime-types: application/json
min-request-size: 2048
response:
enabled: true
# 整合Sentinel
sentinel:
enabled: true
5.5 日志级别
| 级别 | 说明 |
|---|---|
| NONE | 不记录任何日志(默认) |
| BASIC | 记录请求方法、URL、响应状态码、执行时间 |
| HEADERS | BASIC + 请求头和响应头 |
| FULL | 全部信息,包括请求体和响应体 |
java
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
5.6 服务降级
java
// 方式一:fallback
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable Long id);
}
@Component
public class UserClientFallback implements UserClient {
@Override
public User getUser(Long id) {
return new User(-1L, "降级用户");
}
}
// 方式二:fallbackFactory(可获取异常信息)
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable Long id);
}
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUser(Long id) {
log.error("调用user-service失败: {}", cause.getMessage());
return new User(-1L, "服务暂不可用");
}
};
}
}
5.7 底层原理详解
┌─────────────────────────────────────────────────────────────────────────┐
│ OpenFeign 核心组件架构 │
└─────────────────────────────────────────────────────────────────────────┘
┌──────────────────┐
│ @FeignClient接口│
└────────┬─────────┘
│
▼
┌──────────────────┐
│ ReflectiveFeign │ 动态代理生成器
│ (JDK Proxy) │ 根据接口生成代理对象
└────────┬─────────┘
│
▼
┌──────────────────┐
│ FeignInvocation │ 方法调用处理器
│ Handler │ 将方法调用转换为HTTP请求
└────────┬─────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 拦截器链 │
│ ┌──────────────────┐ ┌───────────────────┐ │
│ │RequestInterceptor│ │ResponseInterceptor│ │
│ └──────────────────┘ └───────────────────┘ │
└─────────────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ 负载均衡层 │
│ ┌─────────────────────────────────────┐ │
│ │ LoadBalancerFeignClient │ │
│ │ ↓ │ │
│ │ 从Nacos获取服务实例列表 │ │
│ │ ↓ │ │
│ │ 负载均衡策略选择实例 │ │
│ └─────────────────────────────────────┘ │
└─────────────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ HTTP客户端层 │
│ ┌─────────────┐ ┌─────────┐ ┌───────────┐ │
│ │URLConnection│ │ OkHttp │ │Apache Http│ │
│ │ (默认) │ │(推荐) │ │ Client │ │
│ └─────────────┘ └─────────┘ └───────────┘ │
└────────────────────────────────────────────┘
5.8 HTTP客户端优化(连接池配置)
默认的URLConnection不支持连接池,每次请求都新建连接,性能差。推荐使用OkHttp或Apache HttpClient。
方式一:OkHttp(推荐)
xml
<!-- 1. 添加依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
yaml
# 2. 配置启用
feign:
okhttp:
enabled: true
httpclient:
enabled: false
java
// 3. 自定义OkHttp配置(可选)
@Configuration
@ConditionalOnClass(OkHttpClient.class)
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
.writeTimeout(10, TimeUnit.SECONDS) // 写入超时
.retryOnConnectionFailure(true) // 连接失败重试
.connectionPool(new ConnectionPool(
100, // 最大空闲连接数
5, TimeUnit.MINUTES // 空闲连接存活时间
))
.build();
}
}
方式二:Apache HttpClient
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
yaml
feign:
httpclient:
enabled: true
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 单路由最大连接
connection-timeout: 5000 # 连接超时
time-to-live: 60 # 连接存活时间(秒)
okhttp:
enabled: false
5.9 请求拦截器详解
java
// 全局请求拦截器 - 传递Token、链路追踪等信息
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 1. 传递认证Token
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token)) {
template.header("Authorization", token);
}
}
// 2. 传递链路追踪ID
String traceId = MDC.get("traceId");
if (StringUtils.hasText(traceId)) {
template.header("X-Trace-Id", traceId);
}
// 3. 传递其他自定义头
template.header("X-Request-Source", "feign-client");
}
}
重要注意:异步场景下RequestContextHolder可能为null
java
// 解决异步调用时无法获取Request的问题
@Configuration
public class FeignAsyncConfig {
@Bean
public Executor feignAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
// 关键:使用装饰器传递上下文
executor.setTaskDecorator(runnable -> {
RequestAttributes context = RequestContextHolder.getRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
});
executor.initialize();
return executor;
}
}
5.10 超时与重试机制
yaml
feign:
client:
config:
default:
connectTimeout: 5000 # 连接超时
readTimeout: 10000 # 读取超时
# 针对特定服务配置(覆盖默认)
user-service:
connectTimeout: 3000
readTimeout: 5000
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启重试
clients:
user-service:
retry:
maxRetriesOnSameServiceInstance: 1 # 同一实例重试次数
maxRetriesOnNextServiceInstance: 2 # 切换实例重试次数
retryableStatusCodes: 500,502,503 # 可重试的状态码
超时优先级:Feign配置 > Ribbon配置 > 全局默认
5.11 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Read timeout | 读取超时 | 增大readTimeout或优化下游服务 |
| Connect timeout | 连接超时 | 检查网络或服务是否存活 |
| No instances available | 无可用实例 | 检查服务注册和服务名称 |
| 请求参数丢失 | @PathVariable未指定value | @PathVariable("id") |
| Body传递失败 | GET请求不支持Body | 改用POST或@SpringQueryMap |
| 异步调用头信息丢失 | 线程上下文未传递 | 使用TaskDecorator传递上下文 |
5.12 性能优化清单
yaml
# 最佳实践配置
feign:
# 1. 使用连接池
okhttp:
enabled: true
# 2. 开启压缩
compression:
request:
enabled: true
mime-types: application/json,text/xml
min-request-size: 2048 # 超过2KB才压缩
response:
enabled: true
# 3. 关闭日志(生产环境)
client:
config:
default:
loggerLevel: NONE # 关闭日志提升性能
# 4. 开启Sentinel熔断
sentinel:
enabled: true
# 5. 负载均衡优化
spring:
cloud:
loadbalancer:
cache:
enabled: true # 启用服务列表缓存
ttl: 35s # 缓存时间
六、Seata 详解(分布式事务)
6.1 是什么
Seata是阿里巴巴开源的分布式事务解决方案,提供AT、TCC、SAGA、XA四种事务模式,让分布式事务像本地事务一样简单。
6.2 核心角色
┌─────────────────────────────────────────────────────────────────┐
│ Seata 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ TM │ │ TC │ │ RM │ │
│ │ Transaction │◀────▶│ Transaction │◀────▶ │ Resource │ │
│ │ Manager │ │ Coordinator │ │ Manager │ │
│ │ 事务管理器 │ │ 事务协调器 │ │ 资源管理器 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ 定义事务边界 协调全局事务 管理分支事务 │
│ @GlobalTransactional 提交/回滚决策 本地事务执行 │
│ │
└─────────────────────────────────────────────────────────────────┘
| 角色 | 说明 |
|---|---|
| TC | Transaction Coordinator,事务协调器,维护全局和分支事务状态 |
| TM | Transaction Manager,事务管理器,定义全局事务范围 |
| RM | Resource Manager,资源管理器,管理分支事务的资源 |
6.3 AT模式原理(推荐)
AT模式是Seata默认模式,无代码侵入,基于两阶段提交:
┌────────────────────────────────────────────────────────────────┐
│ AT模式 - 一阶段 │
├────────────────────────────────────────────────────────────────┤
│ 1. 解析SQL,获取表名、条件等信息 │
│ ↓ │
│ 2. 查询前镜像(before image)- 修改前的数据 │
│ ↓ │
│ 3. 执行业务SQL │
│ ↓ │
│ 4. 查询后镜像(after image)- 修改后的数据 │
│ ↓ │
│ 5. 生成undo log,插入UNDO_LOG表 │
│ ↓ │
│ 6. 注册分支事务,获取全局锁 │
│ ↓ │
│ 7. 提交本地事务 │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ AT模式 - 二阶段 │
├────────────────────────────────────────────────────────────────┤
│ 【提交】 │
│ - 删除undo log │
│ - 释放全局锁 │
│ - 异步批量处理,性能高 │
│ │
│ 【回滚】 │
│ - 根据before image生成反向SQL │
│ - 执行反向SQL恢复数据 │
│ - 删除undo log │
│ - 释放全局锁 │
└────────────────────────────────────────────────────────────────┘
6.4 使用示例
java
// 1. 在事务发起方添加 @GlobalTransactional
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient; // Feign客户端
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 1. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setAmount(dto.getAmount());
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
stockClient.deduct(dto.getProductId(), dto.getCount());
// 3. 扣减余额(远程调用)
accountClient.deduct(dto.getUserId(), dto.getAmount());
// 任意一步失败,全局回滚
}
}
6.5 核心配置
yaml
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group # 事务组名称
service:
vgroup-mapping:
my_tx_group: default # 事务组映射到TC集群
registry:
type: nacos
nacos:
server-addr: localhost:8848
namespace: ""
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace: ""
group: SEATA_GROUP
6.6 四种事务模式对比
| 模式 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| AT | 自动补偿 | 无侵入、简单 | 需要数据库支持 | 大多数场景 |
| TCC | Try-Confirm-Cancel | 性能高 | 代码侵入大 | 资金类高一致性 |
| SAGA | 长事务补偿 | 适合长流程 | 隔离性差 | 业务流程长 |
| XA | 两阶段提交 | 强一致 | 性能差、锁时间长 | 传统数据库 |
6.7 TCC模式详解
TCC(Try-Confirm-Cancel)是一种业务层面的两阶段提交,适用于高性能、高一致性要求的场景。
┌─────────────────────────────────────────────────────────────────────────┐
│ TCC 三个阶段 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Try │ │ Confirm │ │ Cancel │
│ 尝试阶段 │ │ 确认阶段 │ │ 取消阶段 │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ - 检查业务可行性 │ │ - 真正执行业务 │ │ - 释放预留资源 │
│ - 预留资源 │ ──▶│ - 提交预留资源 │ or │ - 回滚业务操作 │
│ - 不实际执行 │ │ - 幂等性保证 │ │ - 幂等性保证 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
【示例:转账场景】
Try: 冻结A账户100元,不实际扣款
Confirm: 扣除A已冻结的100元,加到B账户
Cancel: 解冻结A账户的100元
java
// TCC模式代码实现
@LocalTCC
public interface AccountTccService {
/**
* Try - 冻结金额
*/
@TwoPhaseBusinessAction(
name = "deductTcc",
commitMethod = "confirm",
rollbackMethod = "cancel"
)
boolean tryDeduct(
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount
);
/**
* Confirm - 确认扣款
*/
boolean confirm(BusinessActionContext context);
/**
* Cancel - 取消冻结
*/
boolean cancel(BusinessActionContext context);
}
@Service
@Slf4j
public class AccountTccServiceImpl implements AccountTccService {
@Autowired
private AccountMapper accountMapper;
@Override
@Transactional
public boolean tryDeduct(Long userId, BigDecimal amount) {
log.info("Try: 冻结用户{}金额{}", userId, amount);
// 1. 检查余额是否充足
Account account = accountMapper.selectById(userId);
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
// 2. 冻结金额(不实际扣款)
accountMapper.freezeAmount(userId, amount);
return true;
}
@Override
@Transactional
public boolean confirm(BusinessActionContext context) {
Long userId = (Long) context.getActionContext("userId");
BigDecimal amount = (BigDecimal) context.getActionContext("amount");
log.info("Confirm: 确认扣款用户{}金额{}", userId, amount);
// 幂等性检查:确认资源是否已处理
if (isAlreadyConfirmed(context.getXid())) {
return true;
}
// 真正扣款:将冻结金额转为实际扣款
accountMapper.confirmDeduct(userId, amount);
return true;
}
@Override
@Transactional
public boolean cancel(BusinessActionContext context) {
Long userId = (Long) context.getActionContext("userId");
BigDecimal amount = (BigDecimal) context.getActionContext("amount");
log.info("Cancel: 取消冻结用户{}金额{}", userId, amount);
// 幂等性检查
if (isAlreadyCancelled(context.getXid())) {
return true;
}
// 空回滚检查:Try未执行成功,Cancel不需要执行
if (!isTrySuccess(context.getXid())) {
return true;
}
// 解冻金额
accountMapper.unfreezeAmount(userId, amount);
return true;
}
}
TCC三大难点及解决方案:
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 幂等性 | Confirm/Cancel可能被重复调用 | 通过xid+分支事务状态记录判断 |
| 空回滚 | Cancel在Try之前执行 | 检查Try是否执行过,未执行则直接返回 |
| 悬挂 | Try在Cancel之后执行 | 检查是否已回滚,已回滚则Try不执行 |
6.8 UNDO_LOG表结构
AT模式需要在每个业务数据库创建undo_log表。
sql
-- 每个业务库必须创建此表
CREATE TABLE `undo_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`branch_id` BIGINT(20) NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(100) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚信息(前后镜像)',
`log_status` INT(11) NOT NULL COMMENT '日志状态 0-正常 1-全局已完成',
`log_created` DATETIME NOT NULL COMMENT '创建时间',
`log_modified` DATETIME NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='AT模式undo日志表';
rollback_info字段内容示例:
json
{
"branchId": 641789253,
"xid": "192.168.1.1:8091:641789253",
"undoItems": [{
"afterImage": {
"rows": [{"fields": [{"name": "id", "value": 1}, {"name": "balance", "value": 800}]}],
"tableName": "account"
},
"beforeImage": {
"rows": [{"fields": [{"name": "id", "value": 1}, {"name": "balance", "value": 1000}]}],
"tableName": "account"
},
"sqlType": "UPDATE"
}]
}
6.9 全局锁机制详解
┌─────────────────────────────────────────────────────────────────────────┐
│ Seata 全局锁机制 │
└─────────────────────────────────────────────────────────────────────────┘
【问题场景】
事务A在修改记录X,还未提交
事务B也要修改记录X(不在Seata管理下)
如果事务B先提交,事务A回滚时会覆盖事务B的数据 -> 脏写
【解决方案】全局锁(Global Lock)
┌────────────────────┐ ┌────────────────────┐
│ TC Server │ │ lock_table │
├────────────────────┤ ├────────────────────┤
│ │ │ xid: 192.168.1:8091│
│ 维护全局锁表 │ ◀───────── │ table: account │
│ │ │ pk: 1 │
│ 冲突检测 │ │ branch_id: 641789 │
│ │ └────────────────────┘
└────────────────────┘
【全局锁工作流程】
1. 一阶段本地事务提交前,向TC注册分支事务,申请全局锁
2. TC检查该记录是否已被锁定
3. 未被锁定:获取锁成功,本地事务提交
4. 已被锁定:等待重试,超时则回滚
5. 二阶段全局提交/回滚后释放全局锁
sql
-- TC Server端锁表结构
CREATE TABLE `lock_table` (
`row_key` VARCHAR(128) NOT NULL COMMENT '行键:表名+主键值',
`xid` VARCHAR(96) NOT NULL COMMENT '全局事务ID',
`transaction_id` BIGINT NOT NULL COMMENT '事务ID',
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`resource_id` VARCHAR(256) NOT NULL COMMENT '资源ID',
`table_name` VARCHAR(32) NOT NULL COMMENT '表名',
`pk` VARCHAR(36) NOT NULL COMMENT '主键值',
`gmt_create` DATETIME NOT NULL COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL COMMENT '修改时间',
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6.10 TC Server部署
yaml
# seata-server配置(file.conf或registry.conf)
store:
mode: db # 存储模式:file/db/redis
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: root
password: root
min-conn: 5
max-conn: 100
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seata-server
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
sql
-- TC Server数据库表(seata库)
-- 全局事务表
CREATE TABLE `global_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 分支事务表
CREATE TABLE `branch_table` (
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6.11 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| no available service | TC Server未注册到Nacos | 检查TC配置和网络 |
| Global lock acquire failed | 其他事务持有锁 | 增大超时时间或优化业务 |
| undo_log不存在 | 未创建undo_log表 | 在业务库创建undo_log表 |
| 回滚失败 | 数据被篡改 | 检查是否有绕过Seata的修改 |
| 事务超时 | 业务执行太长 | 调整timeout配置 |
| 性能差 | 全局锁竞争激烈 | 优化业务粒度,减少热点数据 |
6.12 性能优化建议
yaml
seata:
# 1. 异步提交(提升性能)
client:
rm:
async-commit-buffer-limit: 10000 # 异步提交队列大小
report-retry-count: 5 # 一阶段上报TC重试次数
tm:
commit-retry-count: 5 # 全局提交重试次数
rollback-retry-count: 5 # 全局回滚重试次数
# 2. 服务端优化
server:
undo:
log-save-days: 7 # undo_log保留天数
log-delete-period: 86400000 # 清理周期
recovery:
committing-retry-period: 1000 # 提交重试周期
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-period: 1000
优化要点:
- 减小事务范围:只包含必要的跨服务调用
- 避免热点数据:设计时尽量分散数据访问
- 选择合适模式:资金类用TCC,其他场景用AT
- TC Server集群:生产环境部署多个TC Server
七、组件协作全景图
┌──────────────────┐
│ Nacos │
│ 注册中心+配置中心│
└────────┬─────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户 │ ──────▶ │ Gateway │ │ User-Service│ │Order-Service│
│ Client │ │ 网关 │ │ 用户服务 │ │ 订单服务 │
└─────────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ ┌────────────┴────────────┐ │
│ │ OpenFeign │ │
│ │ 服务间调用 │ │
│ └────────────┬────────────┘ │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ Sentinel │
│ 限流、熔断、降级 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Seata │
│ 分布式事务 │
└─────────────────────────────────────────────┘
请求流程:
- 用户请求到达Gateway网关
- 网关从Nacos获取服务列表,路由到对应服务
- 服务间通过OpenFeign调用,自动负载均衡
- Sentinel对请求进行限流、熔断保护
- 跨服务事务由Seata保证一致性
- 配置变更通过Nacos配置中心动态推送