首页 > 编程 > Java > 正文

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

2019-11-26 11:54:15
字体:
来源:转载
供稿:网友

一、分布式锁使用场景:

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

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

二、需要的技术:

数据库: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;@Repositorypublic 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实现方法(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表