一、前言
平時使用切面去加分布式鎖,是先開啟事務還是先嘗試獲得鎖?這兩者有啥區別?
業務中怎么控制切面的順序?切面的順序對事務的影響怎么避免?
下面程序分析:
@Override@Transactionalpublic ReceiveH5ActivityPrizeResponse receive(ReceiveH5ActivityPrizeRequest request) {logger.info("xxx:{}", JSON.toJSONString(request));ReceiveH5ActivityPrizeResponse response=new ReceiveH5ActivityPrizeResponse();String lockName="receiveH5ActivityPrize" + request.getActivityId();final DistributeLock lock = jedisLockFactory.getJedisLock(lockName,20, TimeUnit.SECONDS);// 1.加鎖lock.lock();try {//todo // 2 業務邏輯 先判斷是否存在,不存在插入一條數據,存在返回(不插入)} finally {// 3.釋放鎖lock.unLock();}// 4 返回return response;}
分布式鎖失效并不是真正的失效,只是讀到數據,讀取的數據庫數據不是最新的。
@Transactional 注解在執行該方法時開啟一個事物,當執行到3步時,insert 事物務還未提交,因此其它線程進入分布式鎖代碼塊后,繼續會執行2操作,發現數據不存在繼續插入一條新數據,存在兩條記錄,此時數據就會出現 bug 問題。
解決辦法:先加鎖,然后在開啟事物,可以保證安全性。?
二、普通未指定 order 的切面和 @Transactional 的先后順序
????????先說下為啥會考慮到這個,我們可以知道 @Transaction 一般加在具體要執行業務的service 方法上,那如果我要進行并發控制對業務進行加鎖,那么嘗試鎖和開啟事務孰先孰后呢?
@Override@Transactional@RedisLock(key = Constant.FANLI_GRANT_VIP_LOCK, param = "#vipOrderNo")public void grantGdVip(String vipOrderNo) {// 業務邏輯}
????????按照業務流程上來看我們需要先嘗試鎖后開啟事務,因為沒獲得鎖開啟事務需要和數據庫進行交互開啟一個新的事務,平常對業務結果是不會影響的,但是當高并發時是會對數據庫帶來不小壓力。
總結:
????????如果普通切面沒指定 order 會比 transaction 后執行。當鎖或者一些檢查性切面被使用時如果條件不滿足不能進入業務也會導致事務的開啟產生了不必要的消耗,當并發高時尤為明顯。
三、切面的順序對事務的影響怎么避免?
其實避免方式有三種,一種是指定order,一種是把自定義切面移到更外層中,一種是使用編程式事務。
1、指定 Order
@Aspect
@Component
@Slf4j
@Order(1)
public class LockAspect {}
2、移到最外層中
移到更外層中就不用證明了,調用的自然順序,比如放在Controller的方法上。
@PostMapping("/web/cardb/gift/receive")@ApiOperation("B卡贈品領取接口")@TokenAuthentication@RedisLock(key = LockKey.RECEIVE_CARD_B_GIFT, param = "#userInfo.userId")public ApiResultResponse receiveCardBGift(@RequestBody @Valid CardbReceiveGiftRequest request) {// 代碼
}
3、使用編程式事務
?當然可以,調用的自然順序,事務的開啟更加現式。
四、總結
因為聲明式事務比較好用,生產中使用的比較多,只有為了控制事務粒度或者不需要抽出一個新的類(為了使事務生效)才會使用編程式事務。
所以更加傾向于移到更外層,因為指定 order 的前提是你知道事務切面的和不指定order普通切面的順序,同時一旦切面變多比如有統一加鎖切面、統一檢查是否認證切面等需要控制自定義切面順序容易和事務切面搞混,不利于維護,這個也相當于自定義切面和框架前面隔離。這也從一個側面證明了校驗放 controller 的合理性。
五、參考文檔
@Transactional和普通自定義切面執行順序的思考