mongo分布式锁Java实现

    

一、分布式锁使用场景:

  1. 代码部署在多台服务器上,即分布式部署。
  2. 多个进程同步访问一个共享资源。

二、需要的技术:

  1.  数据库:mongo
  2. 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);
    }
}
View Code

  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;

扫描二维码关注公众号,回复: 8068608 查看本文章
 
 

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;
    }
}
View Code

四、设计思路

前提:利用mongo实现id自增,且自增过程为原子操作,即线程安全。

  1. 假设有A、B两个请求同时请求资源。
  2. 当A请求到资源是调用mongo自增 +1,并将结果返回给A,即1。此时结果等于1则表明,A请求过程中没有其他请求请求到资源,将锁资源分配给A。
  3. 当B请求到资源是调用mongo自增 +1,并将结果返回给B,即2。此时结果大于1则表明,B请求过程中有其他请求请求到资源,锁资源不能分配给B。
  4. 这样就是实现了多个请求请求同一个锁并且排队。

关于锁过期时间 :

          如果图中代码1releaseLockExpire(key, System.currentTimeMillis())修改为releaseLockExpire(key),即在释放锁的时候没有传入过期时间,会产生如下情况:

  1. A、B两个请求同时通过条件,进入到代码 1 
  2. B执行完删除操作,进入代码2,并且刚刚获得到锁资源,而此时A可能刚开始执行释放锁的操作。
  3. 此时就会发生,A释放了B刚刚获得的锁,这样B就会失去刚刚获得的锁,而B确没有感知,从而造成逻辑错误。
  4. 而releaseLockExpire(key, System.currentTimeMillis()),即在释放锁的时候判断一下过期时间,这样就不会误删B刚刚获得的锁。

猜你喜欢

转载自www.cnblogs.com/pengthrree/p/11981601.html