mongo分布式锁Java实现
一、分布式锁使用场景:
- 代码部署在多台服务器上,即分布式部署。
 - 多个进程同步访问一个共享资源。
 
二、需要的技术:
- 数据库:mongo
 - java:mongo操作插件类 MongoTemplate(maven引用)
 
三、实现代码:
主实现逻辑及外部调用方法,获得锁调用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,并将结果返回给B,即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刚刚获得的锁。
 

mongo分布式锁Java实现的更多相关文章
- 基于redis的 分布式锁  Java实现
		
package com.hs.services.lock; import java.util.concurrent.TimeUnit; import javax.annotation.Resource ...
 - 关于分布式锁Java常用技术方案
		
前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题. 所以自己结合实际工作中的一些经验和网上 ...
 - 分布式锁 -- Java常用技术方案
		
来自博客园http://www.cnblogs.com/PurpleDream/p/5559352.html , 前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分 ...
 - 基于redis的分布式锁的分析与实践
		
 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...
 - redis分布式锁-spring boot aop+自定义注解实现分布式锁
		
接这这一篇redis分布式锁-java实现末尾,实现aop+自定义注解 实现分布式锁 1.为什么需要 声明式的分布式锁 编程式分布式锁每次实现都要单独实现,但业务量大功能复杂时,使用编程式分布式锁无疑 ...
 - 分布式锁2 Java非常用技术方案探讨之ZooKeeper
		
前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.以自己结合实际工作中的一些经验和网上看到的一些资料 ...
 - Java分布式锁之数据库实现
		
之前的文章<Java分布式锁实现>中列举了分布式锁的3种实现方式,分别是基于数据库实现,基于缓存实现和基于zookeeper实现.三种实现方式各有可取之处,本篇文章就详细讲解一下Java分 ...
 - Java分布式锁实现详解
		
在进行大型网站技术架构设计以及业务实现的过程中,多少都会遇到需要使用分布式锁的情况.那么问题也就接踵而至,哪种分布式锁更适合我们的项目? 下面就这个问题,我做了一些分析: 分布式锁现状: 目前几乎很多 ...
 - Java分布式锁,搞懂分布式锁实现看这篇文章就对了
		
随着微处理机技术的发展,人们只需花几百美元就能买到一个CPU芯片,这个芯片每秒钟执行的指令比80年代最大的大型机的处理机每秒钟所执行的指令还多.如果你愿意付出两倍的价钱,将得到同样的CPU,但它却以更 ...
 
随机推荐
- 极光推送报错time_to_live value should be a non-negative integertime_to_live value should be a non-negative integer
			
文件中修改
 - python全栈开发第6天
			
作业一:1) 开启Linux系统前添加一块大小为15G的SCSI硬盘 2) 开启系统,右击桌面,打开终端 3) 为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分区 ...
 - centos7配置外网
			
1设置网络连接中的NAT网络配置 2虚拟机的网络设置选择NAT连接设置如下,子网IP可通过ipconfig查看本地VMnet8,如我本地VMnet8 ip为:192.168.198.0 3 3.开启虚 ...
 - html添加注释怎么弄?
			
HTML 注释: <!--这是一段注释.注释不会在浏览器中显示.--> 这是一段普通的段落. 快捷键: 我用的是 Notpad++ 添加行注释 Ctrl+K 取消行注释 Ctrl+Shif ...
 - ras 加密及解密
			
rsa 对数据进行加密和解密 #!/usr/bin/env python # coding=utf-8 """ pip3 install rsa "" ...
 - 如何在终端使用后台运行模式启动一个Linux应用程序
			
这是一个篇幅不长但是十分有用的教程,可以帮助你在终端启动一个Linux应用程序,并且使终端窗口不会丢失焦点. 我们有很多可以在Linux系统中打开一个终端窗口的方法,这取决于你的选择以及你的桌面环境. ...
 - String与toString
			
String与toString能将对象转为字符串: 类型 行为描述 Array 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接. Boolean ...
 - Git 常用命令大全-转载
			
一. Git 常用命令速查 git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r ...
 - Beego开启热升级
			
1.打开配置 beego.BConfig.Listen.Graceful = true 2.写入pid 程序入口main()函数里写入pid func writePid() { fileName := ...
 - MYSQL中重命名procedure的一种方法
			
最近有用到对存储过程(procedure)重命名的功能,在网上找了一下资料都没有讲到在mysql中是如何实现的,当然可以删掉再重建,但是应该有别的方法,在"mysql"这个数据库( ...