mongo分布式锁Java实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mongo分布式锁Java实现相关的知识,希望对你有一定的参考价值。

一、分布式锁使用场景:

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

二、需要的技术:

  1.  数据库:mongo
  2. java:mongo操作插件类 MongoTemplate(maven引用),如下:
技术分享
 1 <!--mongodo开始-->
 2         <dependency>
 3             <groupId>org.springframework.data</groupId>
 4             <artifactId>spring-data-mongodb</artifactId>
 5             <version>1.8.2.RELEASE</version>
 6         </dependency>
 7         <dependency>
 8             <groupId>org.springframework.data</groupId>
 9             <artifactId>spring-data-commons</artifactId>
10             <version>1.10.0.RELEASE</version>
11         </dependency>
12         <dependency>
13             <groupId>org.mongodb</groupId>
14             <artifactId>mongo-java-driver</artifactId>
15             <version>2.13.0-rc2</version>
16         </dependency>
17  <!--mongodo结束-->
View Code

 三、实现代码:

  主实现逻辑及外部调用方法,获得锁调用getLock,释放锁调用releaseLock,详情如下:

技术分享
 1 import java.util.HashMap;
 2 import java.util.List;
 3 import java.util.Map;
 4 
 5 
 6 public class MongoDistributedLock {
 7 
 8     static MongoLockDao mongoLockDao;
 9 
10     static {
11         mongoLockDao = SpringBeanUtils.getBean("mongoLockDao");
12     }
13 
14     /**
15      * 获得锁的步骤:
16      * 1、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行;
17      * 2、判断锁资源是否过期,如果过期则释放锁资源;
18      * 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。)
19      * 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁;
20      * !!!注意,不需要锁资源时,及时释放锁资源!!!。
21      *
22      * @param key
23      * @param expire
24      * @return
25      */
26     public static boolean getLock(String key, long expire) {
27         List<MongoLock> mongoLocks = mongoLockDao.getByKey(key);
28         //判断该锁是否被获得,锁已经被其他请求获得,直接返回
29         if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() >= System.currentTimeMillis()) {
30             return false;
31         }
32         //释放过期的锁
33         if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() < System.currentTimeMillis()) {
34             releaseLockExpire(key, System.currentTimeMillis());
35         }
36         //!!(在高并发前提下)在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。
37         Map<String, Object> mapResult = mongoLockDao.incrByWithExpire(key, 1, System.currentTimeMillis() + expire);
38         //如果结果是1,代表当前请求获得锁
39         if ((Integer) mapResult.get("value") == 1) {
40             return true;
41             //如果结果>1,表示当前请求在获取锁的过程中,锁已被其他请求获得。
42         } else if ((Integer) mapResult.get("value") > 1) {
43             return false;
44         }
45         return false;
46     }
47 
48     /**
49      * 释放锁
50      *
51      * @param key
52      */
53     public static void releaseLock(String key) {
54         Map<String, Object> condition = new HashMap<>();
55         condition.put("key", key);
56         mongoLockDao.remove(condition);
57     }
58 
59     /**
60      * 释放过期锁
61      *
62      * @param key
63      * @param expireTime
64      */
65     private static void releaseLockExpire(String key, long expireTime) {
66         mongoLockDao.removeExpire(key, expireTime);
67     }
68 }
View Code

  MongoLockDao实现代码:

技术分享
 1 import org.springframework.data.mongodb.core.FindAndModifyOptions;
 2 import org.springframework.data.mongodb.core.query.Criteria;
 3 import org.springframework.data.mongodb.core.query.Query;
 4 import org.springframework.data.mongodb.core.query.Update;
 5 import org.springframework.stereotype.Repository;
 6 
 7 import java.util.HashMap;
 8 import java.util.List;
 9 import java.util.Map;
10 
11 
12 @Repository
13 public class MongoLockDao extends BaseDaoImpl<MongoLock> {
14 
15 
16     /**
17      * 返回指定key的数据
18      *
19      * @param key
20      * @return
21      */
22     public List<MongoLock> getByKey(String key) {
23         Query query = new Query();
24         query.addCriteria(Criteria.where("key").is(key));
25         return (List<MongoLock>) mongoTemplate.find(query, getClz());
26     }
27 
28 
29     /**
30      * 指定key自增increment(原子加),并设置过期时间
31      *
32      * @param key
33      * @param increment
34      * @param expire
35      * @return
36      */
37     public Map<String, Object> incrByWithExpire(String key, double increment, long expire) {
38         //筛选
39         Query query = new Query();
40         query.addCriteria(new Criteria("key").is(key));
41 
42         //更新
43         Update update = new Update();
44         update.inc("value", increment);
45         update.set("expire", expire);
46         //可选项
47         FindAndModifyOptions options = FindAndModifyOptions.options();
48         //没有则新增
49         options.upsert(true);
50         //返回更新后的值
51         options.returnNew(true);
52         Map<String, Object> resultMap = new HashMap<>();
53         resultMap.put("value", Double.valueOf(((MongoLock)
54                 mongoTemplate.findAndModify(query, update, options, getClz())).getValue()).intValue());
55         resultMap.put("expire", Long.valueOf(((MongoLock)
56                 mongoTemplate.findAndModify(query, update, options, getClz())).getExpire()).longValue());
57         return resultMap;
58     }
59 
60 
61     /**
62      * 根据value删除过期的内容
63      *
64      * @param key
65      * @param expireTime
66      */
67     public void removeExpire(String key, long expireTime) {
68         Query query = new Query();
69         query.addCriteria(Criteria.where("key").is(key));
70         query.addCriteria(Criteria.where("expire").lt(expireTime));
71         mongoTemplate.remove(query, getClz());
72     }
73 
74     public void remove(Map<String, Object> condition) {
75         Query query = new Query();
76         Set<Map.Entry<String, Object>> set = condition.entrySet();
77         int flag = 0;
78         for (Map.Entry<String, Object> entry : set) {
79             query.addCriteria(Criteria.where(entry.getKey()).is(entry.getValue()));
80             flag = flag + 1;
81         }
82         if (flag == 0) {
83             query = null;
84         }
85         mongoTemplate.remove(query, getClz());
86     }
87 
88 }
View Code

  MongoLock实体:

技术分享
 1 public class MongoLock {
 2 
 3     private String key;
 4     private double value;
 5     private long expire;
 6 
 7     public double getValue() {
 8         return value;
 9     }
10 
11     public void setValue(double value) {
12         this.value = value;
13     }
14 
15     public long getExpire() {
16         return expire;
17     }
18 
19     public void setExpire(long expire) {
20         this.expire = expire;
21     }
22 
23     public String getKey() {
24         return key;
25     }
26 
27     public void setKey(String key) {
28         this.key = key;
29     }
30 }
View Code

四、设计思路

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

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

关于锁过期时间 :

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

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

 

 

致谢:感谢您的阅读!转载请加原文链接,谢谢!http://www.cnblogs.com/faaidong/p/7126716.html 

以上是关于mongo分布式锁Java实现的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁(设计模式应用实战)

Redis实现分布式锁(设计模式应用实战)

编程实践分布式锁的实现代码

分布式锁三种解决方案

分布式锁的实现方式和优缺点&Java代码实现

80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)