一、分布式锁使用场景:
- 代码部署在多台服务器上,即分布式部署。
- 多个进程同步访问一个共享资源。
二、需要的技术:
- 数据库: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 } 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 <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 key 29 * @return 30 */ 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 key 42 * @param increment 43 * @param expire 44 * @return 45 */ 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 key 74 * @param expireTime 75 */ 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,并将结果返回给B,即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