一、分布式锁使用场景:
代码部署在多台服务器上,即分布式部署。
多个进程同步访问一个共享资源。
二、需要的技术:
数据库:mongo
java:mongo操作插件类 MongoTemplate(maven引用),如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!--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,详情如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
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实体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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实现方法(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。