mongo分布式锁的bug

最近在研究用mongo作为载体,来实现分布式锁,网上查了一下,相关资料并不多,讨论得最多的一种实现方式思路如下: 获得锁的步骤: * 1、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行; * 2、判断锁资源是否过期,如果过期则释放锁资源; * 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级, 可以忽略。) * 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁; * !!!注意,不需要锁资源时,及时释放锁资源!!!。 具体代码如下: package com.example.demo.common.mongo; import com.mongodb.client.result.DeleteResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; @Component public class MongoLockHandler { private static final Logger logger = LoggerFactory.getLogger(MongoLockHandler.class); @Resource private MongoTemplate mongoTemplate; public boolean lock(String key, long expire) { List locks = this.getLockByKey(key); if (locks.size() > 0) { if (locks.get(0).getExpire() >= System.currentTimeMillis()) {//锁已经被别人获取 return false; } else {//释放过期的锁,以便进行新一轮竞争 this.releaseExpiredLock(key, System.currentTimeMillis()); } } //开始新一轮竞争 int value = this.upsertLock(key, 1, System.currentTimeMillis() + expire); // logger.info("锁为:{}", value); return value == 1 ? true : false; } public boolean unLock(String key) { Query query = new Query(); query.addCriteria(Criteria.where("key").is(key)); DeleteResult result = mongoTemplate.remove(query, MongoLock.class); return result.getDeletedCount() > 0; } private Integer upsertLock(String key, int value, long expireTime) { Query query = new Query(); query.addCriteria(Criteria.where("key").is(key)); Update update = new Update(); update.inc("value", value); update.set("expire", expireTime); FindAndModifyOptions options = new FindAndModifyOptions(); options.upsert(true); //存在则更新,否则插入 options.returnNew(true); //返回更新后的值 //此处貌似不是线程安全的,这种实现方式不能作为分布式锁使用?????????? MongoLock mongoLock = mongoTemplate.findAndModify(query, update, options, MongoLock.class); return mongoLock.getValue(); } private void releaseExpiredLock(String key, long expireTime) { Query query = new Query(); query.addCriteria(Criteria.where("key").is(key)); query.addCriteria(Criteria.where("expire").lt(expireTime)); mongoTemplate.remove(query, MongoLock.class); } private List getLockByKey(String key) { Query query = new Query(); query.addCriteria(Criteria.where("key").is(key)); return mongoTemplate.find(query, MongoLock.class); } } 经过测试,发现这种实现方式是有问题的, 测试很简单,启动两个线程,去竞争锁,代码如下: Runnable r1 = () -> { while (true) { logger.info("aaaaa:{}", mongoLockHandler.lock(LOCK_KEY, 3000)); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable r2 = () -> { while (true) { logger.info("bbbbb:{}", mongoLockHandler.lock(LOCK_KEY, 3000)); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(r1).start(); new Thread(r2).start(); 结果是,这两个线程同时获取到了锁......

猜你喜欢

转载自www.cnblogs.com/EX-JINDAWEI001/p/11359954.html