(六十二)java版spring cloud+spring boot+redis多租戶社交電子商務平臺--redis分佈式鎖

電子商務平臺源碼請加企鵝求求:三五三六二四七二五九。在springcloud項目開發中redis分佈式鎖使用主要有兩個場景redis

1.訂單重複提交或支付提交等,防止刷單spring

2.對某個業務進行鎖定,例如:當用戶同一時間,進行對帳戶充值和提現操做,那麼這裏須要根據用戶ID對帳戶進行鎖定,只有一個完成了才能夠進行第二個。 開發實現方式緩存

1.pom.xml中引入jar包,最好引入到基礎模塊中,其餘模塊通用bash

<!-- redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
複製代碼

建立redis操做類RedisGlobalLock(自定義)分佈式

redis提供RedisTemplate方法spring-boot

redis提供三個方法:ui

(1)lock 獲取鎖並鎖定 本方法是當即獲取鎖狀態,若是獲取成功並鎖定,若是獲取失敗spa

(2)tryLock 嘗試獲取鎖並鎖定 本方式是在指定時間嘗試獲取鎖code

(3)unlock 釋放鎖 當業務處理完畢必須釋放鎖xml

重點:

lock和tryLock區別:lock是實時獲取,tryLock是嘗試在一段時間內一直在獲取

@Service
public class RedisGlobalLock {
   private static Log log = LogFactory.getLog(RedisGlobalLock.class);
   private static final String TYPE_NAME = RedisGlobalLock.class.getTypeName();

   /** 默認30ms嘗試一次 */
   private final static long LOCK_TRY_INTERVAL    = 30L;
   /** 默認嘗試20s */
   private final static long LOCK_TRY_TIMEOUT     = 20 * 1000L;
   /** 單個業務持有鎖的時間30s,防止死鎖 */
   private final static long LOCK_EXPIRE     = 30 * 1000L;

   @Autowired
   private RedisTemplate<String, Object> redisTemplate;

   /**
    * 獲取鎖
    * @param key        鎖Key
    * @return          是否獲取鎖
    */
   public boolean lock(String key) {
      return getLock(key, 0, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
   }

   /**
    * 獲取鎖
    * @param key        鎖Key
    * @param expire      有效期
    * @param expireUnit   有效期時間單位
    * @return          是否獲取鎖
    */
   public boolean lock(String key, long expire, TimeUnit expireUnit) {
      return getLock(key, 0, expire, expireUnit);
   }

   /**
    * 嘗試獲取鎖
    * @param key     鎖Key
    * @return       是否獲取鎖
    */
   public boolean tryLock(String key) {
      return tryLock(key, LOCK_TRY_TIMEOUT, TimeUnit.MILLISECONDS);
   }

   /**
    * 嘗試獲取鎖
    * @param key     鎖Key
    * @param timeout  等待超時時間
    * @param unit    等待超時時間單位
    * @return       是否獲取鎖
    */
   public boolean tryLock(String key, long timeout, TimeUnit unit) {
      // 超時時間轉成毫秒
      timeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
      return getLock(key,timeout, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
   }

   /**
    * 嘗試獲取鎖
    * @param key        鎖Key
    * @param timeout     等待超時時間
    * @param timeoutUnit  等待超時時間單位
    * @param expire      有效期
    * @param expireUnit   有效期時間單位
    * @return
    */
   public boolean tryLock(String key, long timeout, TimeUnit timeoutUnit, long expire, TimeUnit expireUnit) {
      // 超時時間轉成毫秒
      timeout = TimeUnit.MILLISECONDS.convert(timeout, timeoutUnit);
      return getLock(key,timeout, expire, expireUnit);
   }

   /**
    * 釋放鎖
    * @param key  鎖Key
    */
   public void unlock(String key) {
      key = getPrefix(TYPE_NAME) + key;
      Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
      if(null != oldExpireTime && oldExpireTime >= System.currentTimeMillis()) {
         // 大於過時時間,則刪除key
         redisTemplate.delete(key);
      }
   }

   /**
    * 獲取鎖
    * @param key     鎖鍵值
    * @param timeout  超時時間
    * @param time    全局鎖生命週期
    * @param unit    時間單位
    * @return       是否獲取到鎖
    */
   private boolean getLock(String key, long timeout, long time, TimeUnit unit) {
      key = getPrefix(TYPE_NAME) + key;
      try {
         long startTimeMillis = System.currentTimeMillis();
         do {
            long newValue = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
            Boolean isOk = redisTemplate.opsForValue().setIfAbsent(key, newValue);
            if(isOk) {
               // 得到鎖
               redisTemplate.expire(key, time, unit);
               return true;
            }
            // 獲取過時時間
            Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
            if(null == oldExpireTime) {
                    oldExpireTime = 0L;
                }
            if(oldExpireTime >= System.currentTimeMillis()) {
               // 不小於系統時間而且過了超時時間,則不獲取鎖
               if((System.currentTimeMillis() - startTimeMillis) > timeout) {
                  return false;
               }

               // 休眠
               Thread.sleep(LOCK_TRY_INTERVAL);
            }
            // 新的過時時間
            long newExpireTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
            Long currentExpireTime = (Long) redisTemplate.opsForValue().getAndSet(key, newExpireTime);
                if(null == currentExpireTime) {
                    currentExpireTime = 0L;
                }
            if(currentExpireTime.equals(oldExpireTime)) {
               // 獲取到鎖
               redisTemplate.expire(key, time, unit);
               return true;
            }
         } while (true);
      } catch (Exception e) {
         return false;
      }
   }

   /**
    * 獲取緩存標識前綴
    * @param typeName 類名
    * @return 前綴
    */
   protected final String getPrefix(String typeName) {
      return typeName;
   }

}
複製代碼

在業務邏輯層引入redis操做類

@Resource
private RedisGlobalLock redisGlobalLock;
// 一、獲取分佈式鎖防止重複調用 ======================
String key = PayDistributePrefix.PAY_MEMBER_ACCOUNT + memberId;
if(redisGlobalLock.lock(key)) {
    try{
        System.out.println("--處理業務---");
    }catch (Exception e){
        throw e;
    }finally {
        // 四、釋放分佈式鎖 ======================
        redisGlobalLock.unlock(key);
    }
}else{
    // 若是沒有獲取鎖
    Ensure.that(true).isTrue("17000706");
}
複製代碼

全部鎖業務必須釋放鎖,防止死鎖

相關文章
相關標籤/搜索