Heim  >  Artikel  >  Java  >  mongo分布式锁的实例讲解

mongo分布式锁的实例讲解

零下一度
零下一度Original
2017-07-20 10:56:202118Durchsuche

这里的mongod实现的比较简单,它主要是通过在配置服务器结点上创建并维护两个锁定集合(lockpings和锁定),并mongos启动时会在这两个集合中各创建一条数据记录,然后通过维护该记录的状态(包括时间戳,状态)来标识当前mongos结点的锁使用情况。如果多台机器之间想使用一个锁,只要确保锁的名称一致就行,这里就会在configsvr结点的相应集合中共享这一条记录以确保只有一个mongos结点获得了该锁。 

一、分布式锁使用场景:

  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     }   
 /**15      * 获得锁的步骤:16      
 * 1、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行;17      
 * 2、判断锁资源是否过期,如果过期则释放锁资源;18      * 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。)19      * 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁;20      * !!!注意,不需要锁资源时,及时释放锁资源!!!。21      *22      * @param key23      * @param expire24      * @return25      */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 key52      */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 key63      * @param expireTime64      */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 @Repository13 public class MongoLockDao <MongoLock> {14     
 private Class<?> clz;15 16     public Class<?> getClz() {17         
 if (clz == null) {18             //获取泛型的Class对象19             
 clz = ((Class<?>)20                     
 (((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));21         }22         
 return clz;23     }24 25     /**26      * 返回指定key的数据27      *28      
 * @param key29      * @return30      */31     public List<MongoLock> getByKey(String key) {32         
 Query query = new Query();33         query.addCriteria(Criteria.where("key").is(key));34         
 return (List<MongoLock>) mongoTemplate.find(query, getClz());35     }36 37 38     /**39      
 * 指定key自增increment(原子加),并设置过期时间40      *41      * @param key42      
 * @param increment43      * @param expire44      * @return45      */46     
 public Map<String, Object> incrByWithExpire(String key, double increment, long expire) {47         
 //筛选48         Query query = new Query();49         query.addCriteria(new Criteria("key").is(key));50 51         
 //更新52         Update update = new Update();53         update.inc("value", increment);54         
 update.set("expire", expire);55         //可选项56         
 FindAndModifyOptions options = FindAndModifyOptions.options();57         
 //没有则新增58         options.upsert(true);59         
 //返回更新后的值60         options.returnNew(true);61         
 Map<String, Object> resultMap = new HashMap<>();62         
 resultMap.put("value", Double.valueOf(((MongoLock)63                 
 mongoTemplate.findAndModify(query, update, options, getClz())).getValue()).intValue());64         
 resultMap.put("expire", Long.valueOf(((MongoLock)65                 
 mongoTemplate.findAndModify(query, update, options, getClz())).getExpire()).longValue());66         
 return resultMap;67     }68 69 70     /**71      * 根据value删除过期的内容72      *73      
 * @param key74      * @param expireTime75      */76     
 public void removeExpire(String key, long expireTime) {77         
 Query query = new Query();78         query.addCriteria(Criteria.where("key").is(key));79         
 query.addCriteria(Criteria.where("expire").lt(expireTime));80         mongoTemplate.remove(query, getClz());81     }82 83     public void remove(Map<String, Object> condition) {84         Query query = new Query();85         Set<Map.Entry<String, Object>> set = condition.entrySet();86         int flag = 0;87         for (Map.Entry<String, Object> entry : set) {88             query.addCriteria(Criteria.where(entry.getKey()).is(entry.getValue()));89             flag = flag + 1;90         }91         if (flag == 0) {92             query = null;93         }94         mongoTemplate.remove(query, getClz());95     }96 97 }
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刚刚获得的锁。

Das obige ist der detaillierte Inhalt vonmongo分布式锁的实例讲解. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn