seata1.0實現feign降級和全局異常處理的時候事務的回滾
源碼下載
大家可以直接微信掃描上面的二維碼關注我的公衆號,然後回覆seata exception 裏面就會給到源代碼的下載地址同時會附上相應的視頻教程,並定期的與大家分享相關的技術文章。
前言
在我們開發的過程中,如果出現異常,正常我們都會進行try…catch或者配置全局異常的補獲處理,或者我們的分feign的服務降級,如果這時候我們使用分佈式事務seata那麼我們會發現我們的事務不會回滾了,難道使用我們的分佈式事務我們就不能做異常的處理了嗎?很明顯這是不可能的,通過官方大神給的方案通過AOP動態創建/關閉Seata分佈式事務我們找到了解決方案,我們這邊文章的工程是基於基於seata1.0和spring cloud的Greenwich.SR2版本的分佈式事務demo例子的實現全過程這篇文章的基礎上進行改造的。
配置feign的降級
由於我們只需要驗證一個服務降級即可,那麼我們這次就直接驗證我們的訂單模塊的account的服務降級,若是對feign的服務降級有不懂的可以直接看這篇博客[spring cloud的Hoxton.SR1版本的feign的優雅降級的實現],(https://linzhefeng23.blog.csdn.net/article/details/103710038)我們直接在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觸發了事務的降級處理如下所示:
然後我們發現我們的事務並沒有回滾,而是正常執行了,那這很明顯不是我們想要的結果,那這時候怎麼辦呢,我們直接參考我們的官方大神給的方案通過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,然後我們發現我們的事務實現了回滾了,這就達到了我們想要的效果。