seata1.0实现feign降级和全局异常处理的时候事务的回滚



源码下载

seata1.0实现feign降级和全局异常处理的时候事务的回滚_feign

大家可以直接微信扫描上面的二维码关注我的公众号,然后回复seata exception 里面就会给到源代码的下载地址同时会附上相应的视频教程,并定期的与大家分享相关的技术文章

前言

在我们开发的过程中,如果出现异常,正常我们都会进行try…catch或者配置全局异常的补获处理,或者我们的分feign的服务降级,如果这时候我们使用分布式事务seata那么我们会发现我们的事务不会回滚了,难道使用我们的分布式事务我们就不能做异常的处理了吗?很明显这是不可能的,通过官方大神给的方案​​通过AOP动态创建/关闭Seata分布式事务​​​我们找到了解决方案,我们这边文章的工程是基于​​基于seata1.0和spring cloud的Greenwich.SR2版本的分布式事务demo例子的实现全过程​​这篇文章的基础上进行改造的。

配置feign的降级

由于我们只需要验证一个服务降级即可,那么我们这次就直接验证我们的订单模块的account的服务降级,若是对feign的服务降级有不懂的可以直接看这篇博客[spring cloud的Hoxton.SR1版本的feign的优雅降级的实现],我们直接在order-server的feign底下创建一个impl包,同时创建一个AccountApiImpl实现AccountApi,代码如下:

/**
* @author linzf
* @since 2019/12/27
* 类描述:
*/
@Component
public class AccountApiImpl implements AccountApi {


@Override
public String decrease(Long userId, BigDecimal money) {
System.out.println("我被服务降级了,回滚了吗?");
return "我被服务降级了!";
}
}

接着我们修改我们的AccountApi添加我们的fallback 修改以后代码如下:

@FeignClient(value = "account-server",fallback = AccountApiImpl.class)
public interface AccountApi {

/**
* 扣减账户余额
* @param userId 用户id
* @param money 金额
* @return
*/
@RequestMapping("/account/decrease")
String decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

最后我们修改我们的account-server模块的AccountServiceImpl的decrease方法直接在该方法的后面抛出异常,模拟方法调用出错的实现,修改以后代码如下:

@Service("accountServiceImpl")
public class AccountServiceImpl implements AccountService{

private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Autowired
private AccountDao accountDao;
@Autowired
private OrderApi orderApi;

/**
* 扣减账户余额
* @param userId 用户id
* @param money 金额
*/
@Override
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("------->扣减账户开始account中");
//模拟超时异常,全局事务回滚
// try {
// Thread.sleep(30*1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
accountDao.decrease(userId,money);
LOGGER.info("------->扣减账户结束account中");

//修改订单状态,此调用会导致调用成环
LOGGER.info("修改订单状态开始");
String mes = orderApi.update(userId, money.multiply(new BigDecimal("0.09")),0);
LOGGER.info("修改订单状态结束:{}",mes);
throw new RuntimeException("我出错了,会被回滚吗?");
}
}

最后还需要修改order-server的application.yml的配置文件的feign.hystrix.enabled属性的值设置为true,最后启动我们的seata-server、注册中心、account-server、order-server、storage-server,启动完成以后我们直接访问以下的地址:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100,这时候我们发现我们的order-server触发了事务的降级处理如下所示:

seata1.0实现feign降级和全局异常处理的时候事务的回滚_feign_02

然后我们发现我们的事务并没有回滚,而是正常执行了,那这很明显不是我们想要的结果,那这时候怎么办呢,我们直接参考我们的官方大神给的方案​​通过AOP动态创建/关闭Seata分布式事务​​来解决我们的事务不回滚的问题。

实现feign降级的时候事务的回滚

直接在我们的account-server、order-server、storage-server工程底下创建一个config目录,然后创建我们的事务处理切面类【WorkAspect】代码如下:

/**
* @author linzf
* @since 2019/12/27
* 类描述: 用于处理程序调用发生异常的时候由于异常被处理以后无法触发事务,而进行的处理,使之可以正常的触发事务。
*/
@Aspect
@Component
public class WorkAspect {

private final static Logger logger = LoggerFactory.getLogger(WorkAspect.class);

@Before("execution(* io.seata.sample.service.*.*(..))")
public void before(JoinPoint joinPoint) throws TransactionException {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
logger.info("拦截到需要分布式事务的方法," + method.getName());
// 此处可用redis或者定时任务来获取一个key判断是否需要关闭分布式事务
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin(300000, "test-client");
logger.info("创建分布式事务完毕" + tx.getXid());
}

@AfterThrowing(throwing = "e", pointcut = "execution(* io.seata.sample.service.*.*(..))")
public void doRecoveryActions(Throwable e) throws TransactionException {
logger.info("方法执行异常:{}", e.getMessage());
if (!StringUtils.isBlank(RootContext.getXID())) {
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
}

}

这时候我们account-server的service方法中将【模拟超时异常,全局事务回滚】这段的注释给放开,然后直接访问:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100,然后我们发现我们的事务实现了回滚了,这就达到了我们想要的效果。

实现全局异常处理以后事务的回滚

我们直接在我们的account-server底下配置一个【GlobalExceptionsHandler】,代码如下:

/**
* @author linzf
* @since 2019/5/29
* 类描述:全局异常捕获处理
*/
@ControllerAdvice
public class GlobalExceptionsHandler {

private Logger log = LoggerFactory.getLogger(GlobalExceptionsHandler.class);

/**
* 功能描述:全局异常处理
*
* @param e
* @return 返回处理结果
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Object errorHandler(Exception e) throws Exception {
// 此处为属性级的错误日志的处理
if (e instanceof ConstraintViolationException) {
log.info("绑定错误日志为:{}", e.getMessage());
return "请求数据格式错误";
// 此处为方法级别的错误日志处理
} else if (e instanceof MethodArgumentNotValidException) {
log.info("方法级的绑定错误日志为:{}", e.getMessage());
return "请求数据格式错误";
// 此处为全局错误日志的处理
} else {
log.info("错误日志为:{}", e.getMessage());
return "全局异常错误给捕获了!";
}
}


}

这时候我们需要在account-server的service方法中抛出运行时异常,然后我们直接访问:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100,然后我们发现我们的事务实现了回滚了,这就达到了我们想要的效果。