项目集成Seata——自定义全局异常处理器与Feign拦截器+接口测试

338 阅读4分钟

一、代码逻辑详解


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));
}
  • 逻辑

    1. 拦截Spring参数校验异常(MethodArgumentNotValidExceptionBindException)。
    2. 提取所有字段错误信息(FieldError)。
    3. 将错误字段名和消息拼接成字符串列表。
    4. 返回HTTP 200响应,错误码为METHOD_ARGUMENT_NOT_VALID

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));
}
  • 逻辑

    1. 拦截HTTP请求体解析失败异常(如JSON格式错误)。
    2. 直接返回预定义的错误码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()));
}
  • 逻辑

    1. 拦截自定义业务异常TestProjectException
    2. 根据异常中是否包含预定义的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));
}
  • 逻辑

    1. 拦截所有未处理的异常。
    2. 关键点:检查当前是否存在Seata全局事务(通过RootContext.getXID()获取XID)。
    3. 如果存在XID,触发事务回滚(GlobalTransactionContext.reload().rollback())。
    4. 返回通用错误码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,确保分布式事务上下文传递。
  • 条件:仅在项目中存在RequestInterceptorGlobalTransactional类时生效。

请求头注入逻辑
@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);
    }
}
  • 逻辑

    1. 获取当前Seata事务ID(XID)。
    2. 排除特定路径:不向认证接口(CHECK_TOKEN_URICHECK_RBAC_URI)传递XID。
    3. 将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。

四、优化建议

  1. 事务粒度优化

    • 将非核心操作(如日志记录)移出全局事务。
  2. Seata配置调优

    # 调整TC事务日志存储模式
    store.mode=db
    store.db.datasource=druid
    store.db.maxWait=5000
    
  3. 异常处理改进

    • GlobalTransactional注解中指定timeout属性,避免事务长时间悬挂。

总结

通过全局异常处理器与Feign拦截器的配合,系统实现了分布式事务的自动回滚和上下文传递。压测结果表明,在4C8G服务器下,系统能稳定支持300 QPS的正常事务处理,但在异常回滚场景中响应时间显著上升。建议通过事务拆分和Seata配置优化进一步提升性能。