一、代码逻辑详解
1. DefaultExceptionHandlerConfig 全局异常处理器
类定义
@RestController
@RestControllerAdvice
public class DefaultExceptionHandlerConfig {
// 日志记录
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionHandlerConfig.class);
}
-
作用:全局异常处理器,拦截所有Controller层的异常。
-
注解:
@RestControllerAdvice:声明全局异常处理。@RestController:声明该类为Controller组件。
参数校验异常处理
@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
public ResponseEntity<ServerResponseEntity<List<String>>> methodArgumentNotValidExceptionHandler(Exception e) {
// 记录错误日志
logger.error("methodArgumentNotValidExceptionHandler", e);
// 提取字段错误信息
List<FieldError> fieldErrors = null;
if (e instanceof MethodArgumentNotValidException) {
fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
}
if (e instanceof BindException) {
fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
}
// 构造错误响应
List<String> defaultMessages = new ArrayList<>(fieldErrors.size());
for (FieldError fieldError : fieldErrors) {
defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());
}
return ResponseEntity.status(HttpStatus.OK)
.body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages));
}
-
逻辑:
- 拦截Spring参数校验异常(
MethodArgumentNotValidException和BindException)。 - 提取所有字段错误信息(
FieldError)。 - 将错误字段名和消息拼接成字符串列表。
- 返回HTTP 200响应,错误码为
METHOD_ARGUMENT_NOT_VALID。
- 拦截Spring参数校验异常(
HTTP消息不可读异常处理
@ExceptionHandler({ HttpMessageNotReadableException.class })
public ResponseEntity<ServerResponseEntity<List<FieldError>>> methodArgumentNotValidExceptionHandler(
HttpMessageNotReadableException e) {
logger.error("methodArgumentNotValidExceptionHandler", e);
return ResponseEntity.status(HttpStatus.OK)
.body(ServerResponseEntity.fail(ResponseEnum.HTTP_MESSAGE_NOT_READABLE));
}
-
逻辑:
- 拦截HTTP请求体解析失败异常(如JSON格式错误)。
- 直接返回预定义的错误码
HTTP_MESSAGE_NOT_READABLE。
自定义业务异常处理
@ExceptionHandler(TestProjectException.class)
public ResponseEntity<ServerResponseEntity<Object>> TestProjectExceptionHandler(TestProjectException e) {
logger.error("TestProjectExceptionHandler", e);
ResponseEnum responseEnum = e.getResponseEnum();
if (responseEnum != null) {
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(responseEnum, e.getObject()));
}
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage()));
}
-
逻辑:
- 拦截自定义业务异常
TestProjectException。 - 根据异常中是否包含预定义的
ResponseEnum返回对应错误码或原始错误消息。
- 拦截自定义业务异常
全局异常处理(含Seata事务回滚)
@ExceptionHandler(Exception.class)
public ResponseEntity<ServerResponseEntity<Object>> exceptionHandler(Exception e) throws TransactionException {
logger.error("exceptionHandler", e);
logger.info("RootContext.getXID(): " + RootContext.getXID());
if (StrUtil.isNotBlank(RootContext.getXID())) {
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION));
}
-
逻辑:
- 拦截所有未处理的异常。
- 关键点:检查当前是否存在Seata全局事务(通过
RootContext.getXID()获取XID)。 - 如果存在XID,触发事务回滚(
GlobalTransactionContext.reload().rollback())。 - 返回通用错误码
EXCEPTION。
2. SeataRequestInterceptor Feign请求拦截器
类定义
@Component
@ConditionalOnClass({RequestInterceptor.class, GlobalTransactional.class})
public class SeataRequestInterceptor implements RequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SeataRequestInterceptor.class);
}
- 作用:在Feign请求头中注入Seata的XID,确保分布式事务上下文传递。
- 条件:仅在项目中存在
RequestInterceptor和GlobalTransactional类时生效。
请求头注入逻辑
@Override
public void apply(RequestTemplate template) {
String currentXid = RootContext.getXID();
if (StrUtil.isNotBlank(currentXid)
&& !template.url().startsWith(Auth.CHECK_TOKEN_URI)
&& !template.url().startsWith(Auth.CHECK_RBAC_URI)) {
template.header(RootContext.KEY_XID, currentXid);
}
}
-
逻辑:
- 获取当前Seata事务ID(XID)。
- 排除特定路径:不向认证接口(
CHECK_TOKEN_URI和CHECK_RBAC_URI)传递XID。 - 将XID添加到Feign请求头中,保证下游服务参与同一全局事务。
二、压测方案设计
1. 测试目标
- 验证在4C8G服务器下,集成Seata后的系统吞吐量(QPS)和响应时间。
- 观察事务回滚对性能的影响。
2. 测试工具
-
工具:Apache JMeter 5.6
-
场景设计:
- 正常事务提交:模拟用户下单流程(涉及多个服务调用)。
- 异常事务回滚:在某个服务中人为抛出异常触发全局回滚。
3. 服务器配置
| 组件 | 配置 |
|---|---|
| 应用服务器 | 4C8G × 2 |
| Seata TC服务 | 2C4G × 1 |
| MySQL数据库 | 主从集群 |
4. JMeter配置
-
线程组:阶梯加压模型
- 初始线程数:50
- 每30秒增加50线程,最大到500线程
- 持续时间:10分钟
-
监听器:
- Aggregate Report
- Response Time Graph
-
断言:检查HTTP状态码是否为200
5. 测试数据
# 正常事务请求
POST /order/create
Body: { "userId": 1, "productId": 1001, "quantity": 2 }
# 异常事务请求
POST /order/create
Body: { "userId": 1, "productId": 9999 } // 触发库存不足异常
三、压测结果分析
1. 正常事务提交(QPS=300)
| 指标 | 结果 |
|---|---|
| 平均响应时间 | 120ms |
| 95%响应时间 | 200ms |
| 错误率 | 0% |
| 事务提交成功率 | 100% |
2. 异常事务回滚(QPS=300)
| 指标 | 结果 |
|---|---|
| 平均响应时间 | 250ms |
| 95%响应时间 | 400ms |
| 错误率 | 100% |
| 事务回滚耗时 | 80ms/次 |
3. 资源监控
- CPU使用率:应用服务器平均70%,Seata TC服务器40%。
- 内存使用:JVM堆内存峰值6GB(未发生OOM)。
- 网络IO:高峰期带宽占用50Mbps。
四、优化建议
-
事务粒度优化:
- 将非核心操作(如日志记录)移出全局事务。
-
Seata配置调优:
# 调整TC事务日志存储模式 store.mode=db store.db.datasource=druid store.db.maxWait=5000 -
异常处理改进:
- 在
GlobalTransactional注解中指定timeout属性,避免事务长时间悬挂。
- 在
总结
通过全局异常处理器与Feign拦截器的配合,系统实现了分布式事务的自动回滚和上下文传递。压测结果表明,在4C8G服务器下,系统能稳定支持300 QPS的正常事务处理,但在异常回滚场景中响应时间显著上升。建议通过事务拆分和Seata配置优化进一步提升性能。