溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

mongo分布式鎖Java實(shí)現(xiàn)方法(推薦)

發(fā)布時(shí)間:2020-08-19 15:13:26 來源:腳本之家 閱讀:225 作者:jingxian 欄目:編程語言

一、分布式鎖使用場(chǎng)景:

代碼部署在多臺(tái)服務(wù)器上,即分布式部署。

多個(gè)進(jìn)程同步訪問一個(gè)共享資源。

二、需要的技術(shù):

數(shù)據(jù)庫:mongo

java:mongo操作插件類 MongoTemplate(maven引用),如下:

<!--mongodo開始-->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-mongodb</artifactId>
      <version>1.8.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-commons</artifactId>
      <version>1.10.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.mongodb</groupId>
      <artifactId>mongo-java-driver</artifactId>
      <version>2.13.0-rc2</version>
    </dependency>
 <!--mongodo結(jié)束-->

三、實(shí)現(xiàn)代碼:

主實(shí)現(xiàn)邏輯及外部調(diào)用方法,獲得鎖調(diào)用getLock,釋放鎖調(diào)用releaseLock,詳情如下:

import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class MongoDistributedLock {

  static MongoLockDao mongoLockDao;

  static {
    mongoLockDao = SpringBeanUtils.getBean("mongoLockDao");
  }

  /**
   * 獲得鎖的步驟:
   * 1、首先判斷鎖是否被其他請(qǐng)求獲得;如果沒被其他請(qǐng)求獲得則往下進(jìn)行;
   * 2、判斷鎖資源是否過期,如果過期則釋放鎖資源;
   * 3.1、嘗試獲得鎖資源,如果value=1,那么獲得鎖資源正常;(在當(dāng)前請(qǐng)求已經(jīng)獲得鎖的前提下,還可能有其他請(qǐng)求嘗試去獲得鎖,此時(shí)會(huì)導(dǎo)致當(dāng)前鎖的過期時(shí)間被延長(zhǎng),由于延長(zhǎng)時(shí)間在毫秒級(jí),可以忽略。)
   * 3.2、value>1,則表示當(dāng)前請(qǐng)求在嘗試獲取鎖資源過程中,其他請(qǐng)求已經(jīng)獲取了鎖資源,即當(dāng)前請(qǐng)求沒有獲得鎖;
   * ?。?!注意,不需要鎖資源時(shí),及時(shí)釋放鎖資源?。?!。
   *
   * @param key
   * @param expire
   * @return
   */
  public static boolean getLock(String key, long expire) {
    List<MongoLock> mongoLocks = mongoLockDao.getByKey(key);
    //判斷該鎖是否被獲得,鎖已經(jīng)被其他請(qǐng)求獲得,直接返回
    if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() >= System.currentTimeMillis()) {
      return false;
    }
    //釋放過期的鎖
    if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() < System.currentTimeMillis()) {
      releaseLockExpire(key, System.currentTimeMillis());
    }
    //??!(在高并發(fā)前提下)在當(dāng)前請(qǐng)求已經(jīng)獲得鎖的前提下,還可能有其他請(qǐng)求嘗試去獲得鎖,此時(shí)會(huì)導(dǎo)致當(dāng)前鎖的過期時(shí)間被延長(zhǎng),由于延長(zhǎng)時(shí)間在毫秒級(jí),可以忽略。
    Map<String, Object> mapResult = mongoLockDao.incrByWithExpire(key, 1, System.currentTimeMillis() + expire);
    //如果結(jié)果是1,代表當(dāng)前請(qǐng)求獲得鎖
    if ((Integer) mapResult.get("value") == 1) {
      return true;
      //如果結(jié)果>1,表示當(dāng)前請(qǐng)求在獲取鎖的過程中,鎖已被其他請(qǐng)求獲得。
    } else if ((Integer) mapResult.get("value") > 1) {
      return false;
    }
    return false;
  }

  /**
   * 釋放鎖
   *
   * @param key
   */
  public static void releaseLock(String key) {
    Map<String, Object> condition = new HashMap<>();
    condition.put("key", key);
    mongoLockDao.remove(condition);
  }

  /**
   * 釋放過期鎖
   *
   * @param key
   * @param expireTime
   */
  private static void releaseLockExpire(String key, long expireTime) {
    mongoLockDao.removeExpire(key, expireTime);
  }
}

MongoLockDao實(shí)現(xiàn)代碼:

import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Repository
public class MongoLockDao <MongoLock> {
  private Class<?> clz;

  public Class<?> getClz() {
    if (clz == null) {
      //獲取泛型的Class對(duì)象
      clz = ((Class<?>)
          (((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));
    }
    return clz;
  }

  /**
   * 返回指定key的數(shù)據(jù)
   *
   * @param key
   * @return
   */
  public List<MongoLock> getByKey(String key) {
    Query query = new Query();
    query.addCriteria(Criteria.where("key").is(key));
    return (List<MongoLock>) mongoTemplate.find(query, getClz());
  }


  /**
   * 指定key自增increment(原子加),并設(shè)置過期時(shí)間
   *
   * @param key
   * @param increment
   * @param expire
   * @return
   */
  public Map<String, Object> incrByWithExpire(String key, double increment, long expire) {
    //篩選
    Query query = new Query();
    query.addCriteria(new Criteria("key").is(key));

    //更新
    Update update = new Update();
    update.inc("value", increment);
    update.set("expire", expire);
    //可選項(xiàng)
    FindAndModifyOptions options = FindAndModifyOptions.options();
    //沒有則新增
    options.upsert(true);
    //返回更新后的值
    options.returnNew(true);
    Map<String, Object> resultMap = new HashMap<>();
    resultMap.put("value", Double.valueOf(((MongoLock)
        mongoTemplate.findAndModify(query, update, options, getClz())).getValue()).intValue());
    resultMap.put("expire", Long.valueOf(((MongoLock)
        mongoTemplate.findAndModify(query, update, options, getClz())).getExpire()).longValue());
    return resultMap;
  }


  /**
   * 根據(jù)value刪除過期的內(nèi)容
   *
   * @param key
   * @param expireTime
   */
  public void removeExpire(String key, long expireTime) {
    Query query = new Query();
    query.addCriteria(Criteria.where("key").is(key));
    query.addCriteria(Criteria.where("expire").lt(expireTime));
    mongoTemplate.remove(query, getClz());
  }

  public void remove(Map<String, Object> condition) {
    Query query = new Query();
    Set<Map.Entry<String, Object>> set = condition.entrySet();
    int flag = 0;
    for (Map.Entry<String, Object> entry : set) {
      query.addCriteria(Criteria.where(entry.getKey()).is(entry.getValue()));
      flag = flag + 1;
    }
    if (flag == 0) {
      query = null;
    }
    mongoTemplate.remove(query, getClz());
  }

}

MongoLock實(shí)體:

public class MongoLock {

  private String key;
  private double value;
  private long expire;

  public double getValue() {
    return value;
  }

  public void setValue(double value) {
    this.value = value;
  }

  public long getExpire() {
    return expire;
  }

  public void setExpire(long expire) {
    this.expire = expire;
  }

  public String getKey() {
    return key;
  }

  public void setKey(String key) {
    this.key = key;
  }
}

四、設(shè)計(jì)思路

前提:利用mongo實(shí)現(xiàn)id自增,且自增過程為原子操作,即線程安全。

假設(shè)有A、B兩個(gè)請(qǐng)求通過請(qǐng)求資源。

當(dāng)A請(qǐng)求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即1,此時(shí)結(jié)果等于1則表明,A請(qǐng)求過程中沒有其他請(qǐng)求請(qǐng)求到資源,將鎖資源分配給A。

當(dāng)B請(qǐng)求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即2。此時(shí)結(jié)果大于1則表明,B請(qǐng)求過程中有其他請(qǐng)求請(qǐng)求到資源,鎖資源不能分配給B。

這樣就是實(shí)現(xiàn)了多個(gè)請(qǐng)求請(qǐng)求同一個(gè)鎖并且排隊(duì)。

關(guān)于鎖過期時(shí)間 :

如果圖中代碼1releaseLockExpire(key, System.currentTimeMillis())修改為releaseLockExpire(key),即在釋放鎖的時(shí)候沒有傳入過期時(shí)間,會(huì)產(chǎn)生如下情況:

A、B兩個(gè)請(qǐng)求同時(shí)通過條件,進(jìn)入到代碼 1

B執(zhí)行完刪除操作,進(jìn)入代碼2,并且剛剛獲得到鎖資源,而此時(shí)A及有可能剛開始執(zhí)行釋放鎖的操作。

此時(shí)就會(huì)發(fā)生,A釋放了B剛剛獲得的鎖,這樣B就會(huì)失去剛剛獲得的鎖,而B確沒有感知,從而造成邏輯錯(cuò)誤。

而releaseLockExpire(key, System.currentTimeMillis()),即在釋放鎖的時(shí)候判斷一下過期時(shí)間,這樣就不會(huì)誤刪B剛剛獲得的鎖。

mongo分布式鎖Java實(shí)現(xiàn)方法(推薦)

以上這篇mongo分布式鎖Java實(shí)現(xiàn)方法(推薦)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI