mongo分布式锁的实例讲解

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

一、分布式锁使用场景:

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

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

二、需要的技术:

    数据库:mongo

    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自增,且自增过程为原子操作,即线程安全。

    假设有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分布式锁的实例讲解的详细内容,更多请关注其它相关文章!

接受失败等于回归真实的自我,接受失败等于打破完美的面具,

mongo分布式锁的实例讲解

相关文章:

你感兴趣的文章:

标签云: