原理

通过线程安全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. 有效Ajax案例

    <script>$(document).ready(function(){ $("input:submit").click(function(){ $.ajax({ t ...

  2. 555定时器(1)单稳态触发器电路及Multisim实例仿真

    555定时器(Timer)因内部有3个5K欧姆分压电阻而得名,是一种多用途的模数混合集成电路,它能方便地组成施密特触发器.单稳态触发器与多谐振荡器,而且成本低,性能可靠,在各种领域获得了广泛的应用. ...

  3. 『心善渊』Selenium3.0基础 — 11、Selenium对元素常用操作

    目录 1.Selenium对元素常用操作 2.Selenium对元素的其他操作 1.Selenium对元素常用操作 操作 说明 click() 单击元素 send_keys() 模拟输入 clear( ...

  4. 高性能的Redis之对象底层实现原理详解

    对象 在前面的数个章节里, 我们陆续介绍了 Redis 用到的所有主要数据结构, 比如简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合, 等等. Redis 并没有直接使用这些数据结构来实 ...

  5. IP地址与子网的划分

    一.IP地址 1.IP地址的定义 (1).IP地址有32位二进制数组成,一般用点分十进制来表示 (2).IP地址由两部分组成 网络部分(NETWORK) 主机部分(HOST) 2.IP地址的分类 IP ...

  6. Unity接入ShareSDK实现QQ登录和QQ分享、微信分享

    原文链接:Unity接入ShareSDK实现QQ登录和QQ分享.微信分享 由于微信登录需要企业审核,我这里就不说明了,有需要的可以去官网看一下文档,和QQ登录比多了一个打包的步骤. 第一步:到官网申请 ...

  7. 15 自动发布Java项目(Tomcat)

    #!/bin/bash export PAHT=/usr/local/maven/bin:/usr/local/jdk/bin:/usr/local/sbin:/usr/local/bin:/usr/ ...

  8. 什么是forward和include?

    请求包含的例子 第一个Servlet (DispatcherServlet) @Override protected void doGet(HttpServletRequest req, HttpSe ...

  9. Docker:docker国内镜像加速

    创建或修改 /etc/docker/daemon.json 文件,修改为如下形式 { "registry-mirrors": [ "https://registry.do ...

  10. ubuntu docker开启2375端口,支持远程访问

    1.编辑docker文件:/usr/lib/systemd/system/docker.service vi /usr/lib/systemd/system/docker.service 2.Exec ...