原理

通过线程安全findAndModify 实现锁

实现

定义锁存储对象:

/**
* 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); // 如果已过期
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锁对象,如果成功且token一致,说明拿到锁
  • 否则加锁失败
  • 如果未拿到锁,但是锁已过期,尝试删除锁
    • 如果删除成功,再次尝试拿锁
    • 如果失败,说明锁可能已经续期了

释放和续期锁:


@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,说明拿锁成功
  • 否则可以sleep一段时间后再拿锁

完整代码,可到github查看 https://github.com/jadepeng/docker-pipeline/blob/main/pipeline-master/src/main/java/com/github/jadepeng/pipeline/service/impl/MongoLockService.java


感谢您的认真阅读。

如果你觉得有帮助,欢迎点赞支持!

不定期分享软件开发经验,欢迎关注作者, 一起交流软件开发:

java基于mongodb实现分布式锁的更多相关文章

  1. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  2. Java基于Redis的分布式锁

    分布式锁,其实最终还是要保证锁(数据)的一致性,说到数据一致性,基于ZK,ETCD数据一致性中间件做分数是锁,才是王道.但是Redis也能满足最基本的需求. 参考: https://www.cnblo ...

  3. 基于redis的 分布式锁 Java实现

    package com.hs.services.lock; import java.util.concurrent.TimeUnit; import javax.annotation.Resource ...

  4. 基于 Redis 的分布式锁

    前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...

  5. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  6. 基于redis的分布式锁实现

    1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...

  7. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  8. Java使用Redis实现分布式锁来防止重复提交问题

    如何用消息系统避免分布式事务? - 少年阿宾 - BlogJavahttp://www.blogjava.net/stevenjohn/archive/2018/01/04/433004.html [ ...

  9. 基于redis 实现分布式锁(二)

    https://blog.csdn.net/xiaolyuh123/article/details/78551345 分布式锁的解决方式 基于数据库表做乐观锁,用于分布式锁.(适用于小并发) 使用me ...

随机推荐

  1. Manacher(马拉车)————O(n)回文子串

    Manacher 一.背景 1975年,Manacher发明了Manacher算法(中文名:马拉车算法),是一个可以在O(n)的复杂度中返回字符串s中最长回文子串长度的算法,十分巧妙. 让我们举个栗子 ...

  2. 【题解】Luogu P2875 [USACO07FEB]牛的词汇The Cow Lexicon

    题目描述 Few know that the cows have their own dictionary with W (1 ≤ W ≤ 600) words, each containing no ...

  3. Mysql优化(出自官方文档) - 第六篇

    Mysql优化(出自官方文档) - 第六篇 目录 Mysql优化(出自官方文档) - 第六篇 Optimizing Subqueries, Derived Tables, View Reference ...

  4. Unity使用Photon PUN2设置中国区服务器

    原文地址:Unity使用Photon PUN2设置中国区服务器 入门系列 PUN2选择中国区服务器 先搜索中国区官网 选择试用购买 绑定你的Appid 注意: 当你的Appid申请了中国区后,海外的你 ...

  5. testt

    一级标题 二级标题 三级标题 四级标题 l 1

  6. 11 监控MySQL主从状态是否异常

    #!/bin/bash source /etc/profile # 主从同步 # master:binlog # slave:relaylog # 写->master->binlog< ...

  7. 18、mysql读写分离实现的方法

    18.1.mysql读写分离实现的方法: 1.通过程序实现读写分离: php和java程序实现读写分离(性能,效率最佳,推荐); php和java程序都可以通过设置多个连接文件轻松实现对数据库的读写分 ...

  8. Raspberry Pi:树莓派安装Kali2021新版本

    准备材料 树莓派4B kali系统镜像 SDFormatter (格式化工具) Win32DiskImager (镜像拷录工具) 镜像下载 kali下载地址:https://www.offensive ...

  9. 资源:VMware秘钥许可证

    一. 激活密钥 YG5H2-ANZ0H-M8ERY-TXZZZ-YKRV8 UG5J2-0ME12-M89WY-NPWXX-WQH88 UA5DR-2ZD4H-089FY-6YQ5T-YPRX6 GA ...

  10. 在Xshell中文件内容显示乱码

    1.修改系统语言 支持中文 echo $LANG    查看系统语言  默认 en_US.UFT_8 vim /etc/locale.conf    修改配置文件 将LANG的值改为 zh_CN.UT ...