mongo分布式锁Java实现方法(推荐)

一、分布式锁使用场景:

代码部署在多台服务器上,即分布式部署。

多个进程同步访问一个共享资源。

二、需要的技术:

数据库: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刚刚获得的锁。

以上这篇mongo分布式锁Java实现方法(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • redis中使用java脚本实现分布式锁

    redis被大量用在分布式的环境中,自然而然分布式环境下的锁如何解决,立马成为一个问题.例如我们当前的手游项目,服务器端是按业务模块划分服务器的,有应用服,战斗服等,但是这两个vm都有可能同时改变玩家的属性,这如果在同一个vm下面,就很容易加锁,但如果在分布式环境下就没那么容易了,当然利用redis现有的功能也有解决办法,比如redis的脚本. redis在2.6以后的版本中增加了Lua脚本的功能,可以通过eval命令,直接在RedisServer环境中执行Lua脚本,并且可以在Lua脚本中调用

  • Java分布式锁的三种实现方案

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0. 异常实现流程 -- 可能会发生的异常情况 -- 线程1查询,当前left_count为1,则有记录 select * from t_bonus

  • redisson实现分布式锁原理

    Redisson分布式锁 之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看redisson是如何实现锁的. 不同版本实现锁的机制并不相同 引用的redisson最近发布的版本3.2.3,不同的版本可能实现锁的机制并不相同,早期版本好像是采用简单的setnx,getset等常规命令来配置完成,而后期由于redis支持了脚本Lua变更了实现原理. <dependency> <groupId>org.redisson&

  • java使用zookeeper实现的分布式锁示例

    使用zookeeper实现的分布式锁 分布式锁,实现了Lock接口 复制代码 代码如下: package com.concurrent; import java.io.IOException;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CountDownLatch;import java.util.concurrent.TimeU

  • mongo分布式锁Java实现方法(推荐)

    一.分布式锁使用场景: 代码部署在多台服务器上,即分布式部署. 多个进程同步访问一个共享资源. 二.需要的技术: 数据库:mongo java:mongo操作插件类 MongoTemplate(maven引用),如下: <!--mongodo开始--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artif

  • Oracle数据库"记录被另一个用户锁住"解决方法(推荐)

    1.先来看看为什么会出锁住: 数据库是一个多用户使用的共享资源.当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性. 加锁是实现数据库并发控制的一个非常重要的技术.当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁.加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作. 在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X

  • Redis分布式锁python-redis-lock使用方法

    python-redis-lock 多个redis客户端访问同一个redis服务端,控制并发. github:https://pypi.org/project/python-redis-lock/ 在使用这个库之前,需要安装如下: pip install python-redis-lock 使用锁的示例: lock = redis_lock.Lock(conn, "name-of-the-lock") if lock.acquire(blocking=False): print(&qu

  • SpringBoot集成Redisson实现分布式锁的方法示例

    上篇 <SpringBoot 集成 redis 分布式锁优化>对死锁的问题进行了优化,今天介绍的是 redis 官方推荐使用的 Redisson ,Redisson 架设在 redis 基础上的 Java 驻内存数据网格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 键值数据库.功能非常强大,解决了很多分布式架构中的问题. Github的wiki地址: https://github.com/redisson/redisson/wiki 官方文档

  • springcloud如何用Redlock实现分布式锁

    目录 一.redlock简介 二.怎么用java使用 redlock 三.参考资料 之前写过一篇文章<如何在springcloud分布式系统中实现分布式锁? >,由于自己仅仅是阅读了相关的书籍,和查阅了相关的资料,就认为那样的是可行的.那篇文章实现的大概思路是用setNx命令和setEx配合使用. setNx是一个耗时操作,因为它需要查询这个键是否存在,就算redis的百万的qps,在高并发的场景下,这种操作也是有问题的.关于redis实现分布式锁,redis官方推荐使用redlock. 一.

  • 详解如何在springcloud分布式系统中实现分布式锁

    目录 一.简介 二.redis命令介绍 三.实现思路 四.编码实现 五.注意点 六.参考资料 最近在看分布式锁的资料,看了 Josial L的<Redis in Action>的分布式锁的章节.实现思路是利用springcloud结合redis实现分布式锁. 注意:这篇文章有问题,请看这一篇https://www.jb51.net/article/228819.htm 一.简介 一般来说,对数据进行加锁时,程序先通过acquire获取锁来对数据进行排他访问,然后对数据进行一些列的操作,最后需要

  • python如何使用Redis构建分布式锁

    这篇文章主要介绍了python如何使用Redis构建分布式锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在实际应用场景中,我们可能有多个worker,可能在一台机器,也可能分布在不同的机器,但只有一个worker可以同时持有一把锁,这个时候我们就需要用到分布式锁了. 这里推荐python的实现库,Redlock-py(Python 实现). 正常情况下,worker获得锁后,处理自己的任务,完成后自动释放持有的锁,是不是感觉有点熟悉,很容易

  • 详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)

    目录 1. redis在实际的应用中 2.如何使用redis的功能进行实现分布式锁 2.1 redis分布式锁思想 2.1.1设计思想: 2.1.2 根据上面的设计思想进行代码实现 2.2 使用redisson进行实现分布式锁 1. redis在实际的应用中 不仅可以用来缓存数据,在分布式应用开发中,经常被用来当作分布式锁的使用,为什么要用到分布式锁呢? 在分布式的开发中,以电商库存的更新功能进行讲解,在实际的应用中相同功能的消费者是有多个的,假如多个消费者同一时刻要去消费一条数据,假如业务逻辑

  • 通过实例解析Java分布式锁三种实现方法

    分布式锁三种实现方式: 一, 基于数据库实现分布式锁 1. 悲观锁 利用select - where - for update 排他锁 注意: 其他附加功能与实现一基本一致,这里需要注意的是"where name=lock ",name字段必须要走索引,否则会锁表.有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题. 2. 乐观锁 所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update ve

  • Redis实现分布式锁的几种方法总结

    Redis实现分布式锁的几种方法总结 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁. 我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1.现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,

随机推荐