最近開發一個小程序遇到一個需求須要實現分佈式事務管理前端
用戶在使用小程序的過程當中能夠查看景點,對景點地區或者城市標記是否想去,那麼須要統計一個地點被標記的人數,以及記錄某個用戶對某個地點是否標記爲想去,用兩個表存儲數據,一個地點表記錄改地點被標記的次數,一個用戶意向表記錄某個用戶對某個地點是否標記爲想去。因爲可能有多個用戶同時標記一個地點,每一個用戶在前端點擊想去按鈕以後,後臺接收到請求,從數據庫查詢某個城市的標記人數,再加1,而後更新到數據庫。從數據庫查詢標記人數,再加1,而後更新到數據庫這個過程數據庫數據必須加鎖,一次只能一個進程處理。不然數據會出現不一樣步問題redis
我使用的RedLock作分佈式鎖管理,用spring註解事務管理。
在實現過程當中遇到以下兩個映像深入的問題:
一、分佈式鎖與spring註解事務共用產生的問題
二、鎖在事務提交前超時問題spring
最初實現代碼以下:數據庫
public markScenicSpot(){ //設置鎖爲destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); //嘗試獲取鎖 long lockTimeOut = 30; //持有鎖超時時間 **boolean success = lock.tryLock(5, lockTimeOut, TimeUnit.SECONDS);** if (success) { try { //業務邏輯實現 }catch (Exception e){ throw e; } finally{ //釋放鎖 **lock.unlock();** } } else { log.error("獲取鎖失敗!更新失敗!"); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); } }
一、spring註解事務@Transactional和分佈式鎖不能一塊兒使用
這是由於@Transactional是經過方法是否拋出異常來判斷事務是否回滾仍是提交,此時方法已經結束。可是咱們必須在方法結束以前釋放鎖,
所以在釋放鎖以後,此時還沒提交,因爲鎖已經釋放,其餘進程能夠得到鎖,並從數據庫查詢地點標記數,可是此時前一個進程沒有提交數據。該進程查到的數據不是最新的數據。
這個問題我排查的時候花了好久,由於鎖釋放和提交事務之間只要幾毫秒的時間,以前一直覺得這麼短的時間不多是這裏的問題,有懷疑過可是本身又放棄了
儘管這個過程只要很短的時間(我實際測試過程當中這個過程只要幾毫秒),可是高併發的狀況仍是會出問題。小程序
因爲不能使用註解事務,我改成手動事務管理,增長以下代碼。併發
public markScenicSpot(){ //設置鎖爲destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); //嘗試獲取鎖 long lockTimeOut = 30; //持有鎖超時時間 boolean success = lock.tryLock(5, lockTimeOut, TimeUnit.SECONDS); if(success){ **DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事物隔離級別 TransactionStatus status = transactionManager.getTransaction(def); // 得到事務狀態** try { //業務邏輯實現 //...... **//提交事務 transactionManager.commit(status);** }catch (Exception e){ **//回滾事務 transactionManager.rollback(status);** } finally{ //釋放鎖 lock.unlock(); } } else { log.error("獲取鎖失敗!更新失敗!"); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); } }
一、鎖超時問題
在進行手動事務管理以後,解決的同步問題。可是出現另一個問題,鎖超時可是事務仍未提交。因爲此時當前進程鎖超時可是沒有提交,此時其餘進程能夠得到鎖並從數據庫查詢目的地標記數,可是不是更新以後的數據,取得的數據有誤。分佈式
針對鎖超時的狀況,只須要當前進程提交以前增長一個判斷,判斷是否超時,若是超時拋出異常退出便可。
增長以下代碼:高併發
public markScenicSpot(){ //設置鎖爲destId RLock lock = redisson.getLock("Afanti_markScenicSpot_updateCountwantAndCountbeenLock_" + ID); //嘗試獲取鎖 long lockTimeOut = 30; //持有鎖超時時間 boolean success = lock.tryLock(5, lockTimeOut, TimeUnit.SECONDS); **//獲取鎖時間 long getLockTime=System.currentTimeMillis();** if(success){ //事務管理 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事物隔離級別 TransactionStatus status = transactionManager.getTransaction(def); // 得到事務狀態 try { //業務邏輯實現 //...... //提交事務,判斷鎖是否超時 **if(System.currentTimeMillis()-getLockTime<lockTimeOut*1000){ transactionManager.commit(status); log.info("提交事務"); } else { log.error("異常:程序執行時間過長,鎖超時!"); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); }** }catch (Exception e){ //回滾事務 transactionManager.rollback(status); } finally{ //釋放鎖 lock.unlock(); } } else { log.error("獲取鎖失敗!更新失敗!"); throw new BizException(ErrorCodeEnum.PROCESS_DATA_ERROR); } }
高併發狀況下,分佈式事務很容易出問題,要對各類狀況分析是否可能出問題,並要對全部可能出問題的狀況作充分的測試才能保證程序健壯。測試