原理

通过线程安全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. IntelliJ IDEA不好用?那是因为没掌握这些技巧

    想要愉快的coding,一个好的IDE肯定少不了.而对于Java开发者来说,IntelliJ IDEA绝对是Java-IDE的首屈一指的选择(eclipse党还请绕我一命). 从我第一次下载IDEA到 ...

  2. 合宙Luat | Cat.1 Socket数据收不到?学会两招不掉线

    1 服务器收不到Socket数据的原因 Socket是大家使用Cat.1模块常用的功能之一,但Cat.1模块不是直接跟服务器连接,而是通过NAT(即网络地址转换)与服务器连接. 一个会话建立后会在NA ...

  3. centos7安装JDK、CentOS

    1.安装JDK 1.1查看系统是否已有自带的JDK rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 1.2卸载已有安装 如果没有输出信息, ...

  4. 面试侃集合 | DelayQueue篇

    面试官:好久不见啊,上次我们聊完了PriorityBlockingQueue,今天我们再来聊聊和它相关的DelayQueue吧. Hydra:就知道你前面肯定给我挖了坑,DelayQueue也是一个无 ...

  5. Java 提效神器 Stream 的冷门技巧

    Stream 使用这个方法创建一个 Stream 对象. new ArrayList<>().stream() Filter 过滤器,里面传递一个函数,这个函数的返回结果如果为 true ...

  6. Linux-Jumpserver服务

    1.介绍 Jumpserver是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能.基于ssh协议来管理,客户端无需安装agent. 特点: 完全开源,GPL授权 Python编 ...

  7. Zabbix5.0微信报警

    3.1.注测企业微信: 3.2.企业微信注册成功后进入后台管理: 3.3.添加一个部门,并记住部门id: #我这里添加的子部门ID为2 3.4.添加一个用户到上面创建的部门里面(这里采取直接将管理员添 ...

  8. 9.5、zabbix高级操作(1)

    在zabbix-web中删除之前所有监控的主机: 1.zabbix自动发现配置: zabbix-server通过扫描指定范围的ip地址发现zabbix-agent并自动添加监控主机,适用于zabbix ...

  9. FastDFS文件系统迁移和数据恢复

    迁移步骤 打包旧服务器文件的所有文件 定位到旧服务器的tracker和Storage目录,将整个文件夹打包 tar -zcf fdfs-storage-data.tar.gz /fastdfs/sto ...

  10. POJ 1279 Art Gallery 半平面交 多边形的核

    题意:求多边形的核的面积 套模板即可 #include <iostream> #include <cstdio> #include <cmath> #define ...