java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
下面小编就为大家带来一篇 mongo 分布式锁 Java 实现方法 (推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
一、分布式锁使用场景:代码部署在多台服务器上,即分布式部署。
多个进程同步访问一个共享资源。
二、需要的技术:数据库:mongo
java:mongo 操作插件类 MongoTemplate(maven 引用),如下:
三、实现代码:
- <!--mongodo开始-->
- <dependency>
- <groupId>org.springframework.data</groupId>
- <artifactId>spring-data-mongodb</artifactId>
- <version>1.8.2.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.data</groupId>
- <artifactId>spring-data-commons</artifactId>
- <version>1.10.0.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongo-java-driver</artifactId>
- <version>2.13.0-rc2</version>
- </dependency>
- <!--mongodo结束-->
主实现逻辑及外部调用方法,获得锁调用 getLock,释放锁调用 releaseLock,详情如下:
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- public class MongoDistributedLock {
- static MongoLockDao mongoLockDao;
- static {
- mongoLockDao = SpringBeanUtils.getBean("mongoLockDao");
- }
- /**
- * 获得锁的步骤:
- * 1、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行;
- * 2、判断锁资源是否过期,如果过期则释放锁资源;
- * 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。)
- * 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁;
- * !!!注意,不需要锁资源时,及时释放锁资源!!!。
- *
- * @param key
- * @param expire
- * @return
- */
- public static boolean getLock(String key, long expire) {
- List < MongoLock > mongoLocks = mongoLockDao.getByKey(key);
- //判断该锁是否被获得,锁已经被其他请求获得,直接返回
- if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() >= System.currentTimeMillis()) {
- return false;
- }
- //释放过期的锁
- if (mongoLocks.size() > 0 && mongoLocks.get(0).getExpire() < System.currentTimeMillis()) {
- releaseLockExpire(key, System.currentTimeMillis());
- }
- //!!(在高并发前提下)在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。
- Map < String,
- Object > mapResult = mongoLockDao.incrByWithExpire(key, 1, System.currentTimeMillis() + expire);
- //如果结果是1,代表当前请求获得锁
- if ((Integer) mapResult.get("value") == 1) {
- return true;
- //如果结果>1,表示当前请求在获取锁的过程中,锁已被其他请求获得。
- } else if ((Integer) mapResult.get("value") > 1) {
- return false;
- }
- return false;
- }
- /**
- * 释放锁
- *
- * @param key
- */
- public static void releaseLock(String key) {
- Map < String,
- Object > condition = new HashMap < >();
- condition.put("key", key);
- mongoLockDao.remove(condition);
- }
- /**
- * 释放过期锁
- *
- * @param key
- * @param expireTime
- */
- private static void releaseLockExpire(String key, long expireTime) {
- mongoLockDao.removeExpire(key, expireTime);
- }
- }
MongoLockDao 实现代码:
- import org.springframework.data.mongodb.core.FindAndModifyOptions;
- 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.Repository;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;@Repository public class MongoLockDao < MongoLock > {
- private Class < ?>clz;
- public Class < ?>getClz() {
- if (clz == null) {
- //获取泛型的Class对象
- clz = ((Class < ?>)(((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));
- }
- return clz;
- }
- /**
- * 返回指定key的数据
- *
- * @param key
- * @return
- */
- public List < MongoLock > getByKey(String key) {
- Query query = new Query();
- query.addCriteria(Criteria.where("key").is(key));
- return (List < MongoLock > ) mongoTemplate.find(query, getClz());
- }
- /**
- * 指定key自增increment(原子加),并设置过期时间
- *
- * @param key
- * @param increment
- * @param expire
- * @return
- */
- public Map < String,
- Object > incrByWithExpire(String key, double increment, long expire) {
- //筛选
- Query query = new Query();
- query.addCriteria(new Criteria("key").is(key));
- //更新
- Update update = new Update();
- update.inc("value", increment);
- update.set("expire", expire);
- //可选项
- FindAndModifyOptions options = FindAndModifyOptions.options();
- //没有则新增
- options.upsert(true);
- //返回更新后的值
- options.returnNew(true);
- Map < String,
- Object > resultMap = new HashMap < >();
- resultMap.put("value", Double.valueOf(((MongoLock) mongoTemplate.findAndModify(query, update, options, getClz())).getValue()).intValue());
- resultMap.put("expire", Long.valueOf(((MongoLock) mongoTemplate.findAndModify(query, update, options, getClz())).getExpire()).longValue());
- return resultMap;
- }
- /**
- * 根据value删除过期的内容
- *
- * @param key
- * @param expireTime
- */
- public void removeExpire(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, getClz());
- }
- public void remove(Map < String, Object > condition) {
- Query query = new Query();
- Set < Map.Entry < String,
- Object >> set = condition.entrySet();
- int flag = 0;
- for (Map.Entry < String, Object > entry: set) {
- query.addCriteria(Criteria.where(entry.getKey()).is(entry.getValue()));
- flag = flag + 1;
- }
- if (flag == 0) {
- query = null;
- }
- mongoTemplate.remove(query, getClz());
- }
- }
MongoLock 实体:
四、设计思路
- public class MongoLock {
- private String key;
- private double value;
- private long expire;
- public double getValue() {
- return value;
- }
- public void setValue(double value) {
- this.value = value;
- }
- public long getExpire() {
- return expire;
- }
- public void setExpire(long expire) {
- this.expire = expire;
- }
- public String getKey() {
- return key;
- }
- public void setKey(String key) {
- this.key = key;
- }
- }
前提:利用 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 分布式锁 Java 实现方法 (推荐) 就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持 PHPERZ。
来源: http://www.phperz.com/article/18/0101/356607.html