mongo分布式鎖Java實(shí)現(xiàn)方法(推薦)
一、分布式鎖使用場(chǎng)景:
代碼部署在多臺(tái)服務(wù)器上,即分布式部署。
多個(gè)進(jìn)程同步訪問(wèn)一個(gè)共享資源。
二、需要的技術(shù):
數(shù)據(jù)庫(kù):mongo
java:mongo操作插件類(lèi) MongoTemplate(maven引用),如下:
<!--mongodo開(kāi)始-->
<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)求獲得;如果沒(méi)被其他請(qǐng)求獲得則往下進(jìn)行;
* 2、判斷鎖資源是否過(guò)期,如果過(guò)期則釋放鎖資源;
* 3.1、嘗試獲得鎖資源,如果value=1,那么獲得鎖資源正常;(在當(dāng)前請(qǐng)求已經(jīng)獲得鎖的前提下,還可能有其他請(qǐng)求嘗試去獲得鎖,此時(shí)會(huì)導(dǎo)致當(dāng)前鎖的過(guò)期時(shí)間被延長(zhǎng),由于延長(zhǎng)時(shí)間在毫秒級(jí),可以忽略。)
* 3.2、value>1,則表示當(dāng)前請(qǐng)求在嘗試獲取鎖資源過(guò)程中,其他請(qǐng)求已經(jīng)獲取了鎖資源,即當(dāng)前請(qǐng)求沒(méi)有獲得鎖;
* ?。。∽⒁?,不需要鎖資源時(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;
}
//釋放過(guò)期的鎖
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)前鎖的過(guò)期時(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)求在獲取鎖的過(guò)程中,鎖已被其他請(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);
}
/**
* 釋放過(guò)期鎖
*
* @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è)置過(guò)期時(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();
//沒(méi)有則新增
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刪除過(guò)期的內(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自增,且自增過(guò)程為原子操作,即線程安全。
假設(shè)有A、B兩個(gè)請(qǐng)求通過(guò)請(qǐng)求資源。
當(dāng)A請(qǐng)求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即1,此時(shí)結(jié)果等于1則表明,A請(qǐng)求過(guò)程中沒(méi)有其他請(qǐng)求請(qǐng)求到資源,將鎖資源分配給A。
當(dāng)B請(qǐng)求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即2。此時(shí)結(jié)果大于1則表明,B請(qǐng)求過(guò)程中有其他請(qǐng)求請(qǐng)求到資源,鎖資源不能分配給B。
這樣就是實(shí)現(xiàn)了多個(gè)請(qǐng)求請(qǐng)求同一個(gè)鎖并且排隊(duì)。
關(guān)于鎖過(guò)期時(shí)間 :
如果圖中代碼1releaseLockExpire(key, System.currentTimeMillis())修改為releaseLockExpire(key),即在釋放鎖的時(shí)候沒(méi)有傳入過(guò)期時(shí)間,會(huì)產(chǎn)生如下情況:
A、B兩個(gè)請(qǐng)求同時(shí)通過(guò)條件,進(jìn)入到代碼 1
B執(zhí)行完刪除操作,進(jìn)入代碼2,并且剛剛獲得到鎖資源,而此時(shí)A及有可能剛開(kāi)始執(zhí)行釋放鎖的操作。
此時(shí)就會(huì)發(fā)生,A釋放了B剛剛獲得的鎖,這樣B就會(huì)失去剛剛獲得的鎖,而B(niǎo)確沒(méi)有感知,從而造成邏輯錯(cuò)誤。
而releaseLockExpire(key, System.currentTimeMillis()),即在釋放鎖的時(shí)候判斷一下過(guò)期時(shí)間,這樣就不會(huì)誤刪B剛剛獲得的鎖。

以上這篇mongo分布式鎖Java實(shí)現(xiàn)方法(推薦)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java Spring Cloud Bus 實(shí)現(xiàn)配置實(shí)時(shí)更新詳解
這篇文章主要介紹了SpringCloud Bus如何實(shí)現(xiàn)配置刷新,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09
詳解springboot設(shè)置默認(rèn)參數(shù)Springboot.setDefaultProperties(map)不生效解決
這篇文章主要介紹了詳解springboot設(shè)置默認(rèn)參數(shù)Springboot.setDefaultProperties(map)不生效解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
SpringBoot實(shí)現(xiàn)上傳文件到AWS S3的代碼
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)上傳文件到AWS S3的代碼,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2020-10-10
Spring使用Jackson實(shí)現(xiàn)轉(zhuǎn)換XML與Java對(duì)象
這篇文章主要為大家詳細(xì)介紹了Spring如何使用Jackson實(shí)現(xiàn)轉(zhuǎn)換XML與Java對(duì)象,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02
Java實(shí)現(xiàn)redis分布式鎖的三種方式
本文主要介紹了Java實(shí)現(xiàn)redis分布式鎖的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
idea2023創(chuàng)建JavaWeb教程之右鍵沒(méi)有Servlet的問(wèn)題解決
最近在寫(xiě)一個(gè)javaweb項(xiàng)目,但是在IDEA中創(chuàng)建好項(xiàng)目后,在搭建結(jié)構(gòu)的時(shí)候創(chuàng)建servlet文件去沒(méi)有選項(xiàng),所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于idea2023創(chuàng)建JavaWeb教程之右鍵沒(méi)有Servlet問(wèn)題的解決方法,需要的朋友可以參考下2023-10-10
關(guān)于IDEA關(guān)聯(lián)數(shù)據(jù)庫(kù)的問(wèn)題
這篇文章主要介紹了IDEA關(guān)聯(lián)數(shù)據(jù)庫(kù)的相關(guān)知識(shí),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
java計(jì)算兩個(gè)時(shí)間相差天數(shù)的方法匯總
這篇文章主要介紹了java計(jì)算兩個(gè)時(shí)間相差天數(shù)的方法,感興趣的小伙伴們可以參考一下2015-11-11
解決使用@ManyToMany查詢(xún)數(shù)據(jù)時(shí)的死循環(huán)問(wèn)題
這篇文章主要介紹了解決使用@ManyToMany查詢(xún)數(shù)據(jù)時(shí)的死循環(huán)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
詳解如何在Spring Boot啟動(dòng)后執(zhí)行指定代碼
這篇文章主要介紹了在Spring Boot啟動(dòng)后執(zhí)行指定代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

