mongo分布式锁Java实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mongo分布式锁Java实现相关的知识,希望对你有一定的参考价值。
一、分布式锁使用场景:
- 代码部署在多台服务器上,即分布式部署。
- 多个进程同步访问一个共享资源。
二、需要的技术:
- 数据库: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结束-->
三、实现代码:
主实现逻辑及外部调用方法,获得锁调用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 }
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 }
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 }
四、设计思路
前提:利用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刚刚获得的锁。
致谢:感谢您的阅读!转载请加原文链接,谢谢!http://www.cnblogs.com/faaidong/p/7126716.html
以上是关于mongo分布式锁Java实现的主要内容,如果未能解决你的问题,请参考以下文章