mongo分布式锁Java实现方法(推荐)
作者:jingxian 发布时间:2023-06-30 21:28:43
一、分布式锁使用场景:
代码部署在多台服务器上,即分布式部署。
多个进程同步访问一个共享资源。
二、需要的技术:
数据库:mongo
java:mongo操作插件类 MongoTemplate(maven引用),如下:
<!--mongodo开始-->
<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结束-->
三、实现代码:
主实现逻辑及外部调用方法,获得锁调用getLock,释放锁调用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、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行;
* 2、判断锁资源是否过期,如果过期则释放锁资源;
* 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。)
* 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁;
* !!!注意,不需要锁资源时,及时释放锁资源!!!。
*
* @param key
* @param expire
* @return
*/
public static boolean getLock(String key, long expire) {
List<MongoLock> mongoLocks = mongoLockDao.getByKey(key);
//判断该锁是否被获得,锁已经被其他请求获得,直接返回
if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() >= System.currentTimeMillis()) {
return false;
}
//释放过期的锁
if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() < System.currentTimeMillis()) {
releaseLockExpire(key, System.currentTimeMillis());
}
//!!(在高并发前提下)在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。
Map<String, Object> mapResult = mongoLockDao.incrByWithExpire(key, 1, System.currentTimeMillis() + expire);
//如果结果是1,代表当前请求获得锁
if ((Integer) mapResult.get("value") == 1) {
return true;
//如果结果>1,表示当前请求在获取锁的过程中,锁已被其他请求获得。
} 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);
}
/**
* 释放过期锁
*
* @param key
* @param expireTime
*/
private static void releaseLockExpire(String key, long expireTime) {
mongoLockDao.removeExpire(key, expireTime);
}
}
MongoLockDao实现代码:
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对象
clz = ((Class<?>)
(((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));
}
return clz;
}
/**
* 返回指定key的数据
*
* @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(原子加),并设置过期时间
*
* @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);
//可选项
FindAndModifyOptions options = FindAndModifyOptions.options();
//没有则新增
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;
}
/**
* 根据value删除过期的内容
*
* @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实体:
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;
}
}
四、设计思路
前提:利用mongo实现id自增,且自增过程为原子操作,即线程安全。
假设有A、B两个请求通过请求资源。
当A请求到资源是调用mongo自增 +1,并将结果返回给A,即1,此时结果等于1则表明,A请求过程中没有其他请求请求到资源,将锁资源分配给A。
当B请求到资源是调用mongo自增 +1,并将结果返回给A,即2。此时结果大于1则表明,B请求过程中有其他请求请求到资源,锁资源不能分配给B。
这样就是实现了多个请求请求同一个锁并且排队。
关于锁过期时间 :
如果图中代码1releaseLockExpire(key, System.currentTimeMillis())修改为releaseLockExpire(key),即在释放锁的时候没有传入过期时间,会产生如下情况:
A、B两个请求同时通过条件,进入到代码 1
B执行完删除操作,进入代码2,并且刚刚获得到锁资源,而此时A及有可能刚开始执行释放锁的操作。
此时就会发生,A释放了B刚刚获得的锁,这样B就会失去刚刚获得的锁,而B确没有感知,从而造成逻辑错误。
而releaseLockExpire(key, System.currentTimeMillis()),即在释放锁的时候判断一下过期时间,这样就不会误删B刚刚获得的锁。


猜你喜欢
- 第一步:代码混淆(注意引入的第三方jar)在新版本的ADT创建项目时,混码的文件不再是proguard.cfg,而是project.prop
- /// <summary>/// 获取本机在局域网的IP地址/// </summary>/// <return
- 有时候你希望在一个嵌套循环的外层循环中执行Continue操作。例如,假设你有一连串的标准,和一堆items。并且你希望找到一个符合每个标准
- C#移除字符串中的不可见Unicode字符 背景最近发现某个数据采集的系统拿下来的数据,有些字段的JSON被莫名截断了,导致后续数
- Maven --> Gradle首先安装gradle:Mac安 * rew install gradleUbuntu安装apt inst
- 多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用
- 前言现在大部分App底部都有一个菜单,实现这个功能也有好多办法:- TabHost+Fragment - RadioGroup+Fragme
- DialogFragment的基本用法1. 创建DialogFragmentpublic class DialogA extends Dia
- 1.传递引用在一个方法中将一个对象的引用传递给另外一个方法,引用指向的对象是同一个public class Person {int age;
- 本文实例为大家分享了Android倒计时的开始与停止,剩余时分秒的展示效果,供大家参考,具体内容如下1.声明开启倒计时相关方法Handler
- 一、介绍搜索了关于spring boot+vue的前后端分离实践项目很多,但是对于最基础登录功能的博客却是几年前的了。于是学习了好几个大神的
- 1、概念向下转型就是父类对象转成子类对象。我们把一个父类引用 Animal类型的引用 给了一个 Bird类型 的引用,这就是向下转型2、格式
- 需求:当时间在凌晨0点至0点5分之间程序不执行。也就是实现判断当前时间点是否在
- 前言使用SpringBoot来开发项目相对于传统模式,要快速优雅许多,相信目前国内绝大部分web项目的开发还没有使用SpringBoot来做
- 在练习Java的Scanner时,EditPlus如何读取从键盘输入的数呢?例如如下程序,编译通过,运行时却输入不了数据:package m
- Spring Boot 程序优化一、延迟初始化Bean一般在 SpringBoot 中都拥有很多的耗时任务,比如数据库建立连接、初始线程池的
- 我就废话不多说了,大家还是直接看代码吧~import com.alibaba.fastjson.JSON;import java.util.
- 我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其
- 前言我相信很多Android开发同学都遇到过这样的需求: 1.实现一个Splash界面,界面上有应用相关的背景图片和一个开始按钮.
- Thread dumps(线程转储)能帮助我们判断 CPU 峰值、死锁、内存异常、应用反应迟钝、响应时间变长和其他系统问题。一些在线的分析工