java基于mongodb實(shí)現(xiàn)分布式鎖的示例代碼
原理
通過(guò)線(xiàn)程安全findAndModify 實(shí)現(xiàn)鎖
實(shí)現(xiàn)
定義鎖存儲(chǔ)對(duì)象:
/** * mongodb 分布式鎖 */ @Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "distributed-lock-doc") public class LockDocument { @Id private String id; private long expireAt; private String token; }
定義Lock API:
public interface LockService { String acquire(String key, long expiration); boolean release(String key, String token); boolean refresh(String key, String token, long expiration); }
獲取鎖:
@Override public String acquire(String key, long expiration) { Query query = Query.query(Criteria.where("_id").is(key)); String token = this.generateToken(); Update update = new Update() .setOnInsert("_id", key) .setOnInsert("expireAt", System.currentTimeMillis() + expiration) .setOnInsert("token", token); FindAndModifyOptions options = new FindAndModifyOptions().upsert(true) .returnNew(true); LockDocument doc = mongoTemplate.findAndModify(query, update, options, LockDocument.class); boolean locked = doc.getToken() != null && doc.getToken().equals(token); // 如果已過(guò)期 if (!locked && doc.getExpireAt() < System.currentTimeMillis()) { DeleteResult deleted = this.mongoTemplate.remove( Query.query(Criteria.where("_id").is(key) .and("token").is(doc.getToken()) .and("expireAt").is(doc.getExpireAt())), LockDocument.class); if (deleted.getDeletedCount() >= 1) { // 成功釋放鎖, 再次嘗試獲取鎖 return this.acquire(key, expiration); } } log.debug("Tried to acquire lock for key {} with token {} . Locked: {}", key, token, locked); return locked ? token : null; }
原理:
- 先嘗試upsert鎖對(duì)象,如果成功且token一致,說(shuō)明拿到鎖
- 否則加鎖失敗
- 如果未拿到鎖,但是鎖已過(guò)期,嘗試刪除鎖
- 如果刪除成功,再次嘗試拿鎖
- 如果失敗,說(shuō)明鎖可能已經(jīng)續(xù)期了
釋放和續(xù)期鎖:
@Override public boolean release(String key, String token) { Query query = Query.query(Criteria.where("_id").is(key) .and("token").is(token)); DeleteResult deleted = mongoTemplate.remove(query, LockDocument.class); boolean released = deleted.getDeletedCount() == 1; if (released) { log.debug("Remove query successfully affected 1 record for key {} with token {}", key, token); } else if (deleted.getDeletedCount() > 0) { log.error("Unexpected result from release for key {} with token {}, released {}", key, token, deleted); } else { log.error("Remove query did not affect any records for key {} with token {}", key, token); } return released; } @Override public boolean refresh(String key, String token, long expiration) { Query query = Query.query(Criteria.where("_id").is(key) .and("token").is(token)); Update update = Update.update("expireAt", System.currentTimeMillis() + expiration); UpdateResult updated = mongoTemplate.updateFirst(query, update, LockDocument.class); final boolean refreshed = updated.getModifiedCount() == 1; if (refreshed) { log.debug("Refresh query successfully affected 1 record for key {} " + "with token {}", key, token); } else if (updated.getModifiedCount() > 0) { log.error("Unexpected result from refresh for key {} with token {}, " + "released {}", key, token, updated); } else { log.warn("Refresh query did not affect any records for key {} with token {}. " + "This is possible when refresh interval fires for the final time " + "after the lock has been released", key, token); } return refreshed; }
使用
private LockService lockService; private void tryAcquireLockAndSchedule() { while (!this.stopSchedule) { // 嘗試拿鎖 this.token = this.lockService.acquire(SCHEDULER_LOCK, 20000); if (this.token != null) { // 拿到鎖 } else { // 等待LOCK_EXPIRATION, 再次嘗試 Thread.sleep(LOCK_EXPIRATION); } } }
- 先嘗試拿鎖,如果獲取到token,說(shuō)明拿鎖成功
- 否則可以sleep一段時(shí)間后再拿鎖
到此這篇關(guān)于java基于mongodb實(shí)現(xiàn)分布式鎖的示例代碼的文章就介紹到這了,更多相關(guān)java mongodb實(shí)現(xiàn)分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA性能優(yōu)化設(shè)置(解決卡頓問(wèn)題)
在我們?nèi)粘J褂肐DEA進(jìn)行開(kāi)發(fā)時(shí),可能會(huì)遇到許多卡頓的瞬間,本文主要介紹了IDEA性能優(yōu)化設(shè)置,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2023-05-05springmvc圖片上傳及json數(shù)據(jù)轉(zhuǎn)換過(guò)程詳解
這篇文章主要介紹了springmvc圖片上傳及json數(shù)據(jù)轉(zhuǎn)換過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10解決Spring?Boot應(yīng)用打包后文件訪(fǎng)問(wèn)問(wèn)題
在Spring Boot項(xiàng)目的開(kāi)發(fā)過(guò)程中,一個(gè)常見(jiàn)的挑戰(zhàn)是如何有效地訪(fǎng)問(wèn)和操作資源文件,本文就來(lái)介紹一下解決Spring?Boot應(yīng)用打包后文件訪(fǎng)問(wèn)問(wèn)題,感興趣的可以了解一下2024-01-01Java開(kāi)發(fā)之普通web項(xiàng)目轉(zhuǎn)為Maven項(xiàng)目的方法
這篇文章主要給大家介紹了關(guān)于Java開(kāi)發(fā)之普通web項(xiàng)目轉(zhuǎn)為Maven項(xiàng)目的相關(guān)資料,文中通過(guò)圖文將轉(zhuǎn)換的方法步驟介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12java通過(guò)JFrame做一個(gè)登錄系統(tǒng)的界面完整代碼示例
這篇文章主要介紹了java通過(guò)JFrame做一個(gè)登錄系統(tǒng)的界面完整代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12IDEA運(yùn)行導(dǎo)入的javaweb項(xiàng)目tomcat正常,但是運(yùn)行失敗404問(wèn)題
這篇文章主要介紹了IDEA運(yùn)行導(dǎo)入的javaweb項(xiàng)目tomcat正常但是運(yùn)行失敗404問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07