一、分布式鎖使用場景:
代碼部署在多臺服務(wù)器上,即分布式部署。
多個進程同步訪問一個共享資源。
二、需要的技術(shù):
數(shù)據(jù)庫: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結(jié)束--> |
三、實現(xiàn)代碼:
主實現(xiàn)邏輯及外部調(diào)用方法,獲得鎖調(diào)用getLock,釋放鎖調(diào)用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,那么獲得鎖資源正常;(在當前請求已經(jīng)獲得鎖的前提下,還可能有其他請求嘗試去獲得鎖,此時會導(dǎo)致當前鎖的過期時間被延長,由于延長時間在毫秒級,可以忽略。) * 3.2、value>1,則表示當前請求在嘗試獲取鎖資源過程中,其他請求已經(jīng)獲取了鎖資源,即當前請求沒有獲得鎖; * !!!注意,不需要鎖資源時,及時釋放鎖資源!!!。 * * @param key * @param expire * @return */ public static boolean getLock(String key, long expire) { List<MongoLock> mongoLocks = mongoLockDao.getByKey(key); //判斷該鎖是否被獲得,鎖已經(jīng)被其他請求獲得,直接返回 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()); } //!!(在高并發(fā)前提下)在當前請求已經(jīng)獲得鎖的前提下,還可能有其他請求嘗試去獲得鎖,此時會導(dǎo)致當前鎖的過期時間被延長,由于延長時間在毫秒級,可以忽略。 Map<String, Object> mapResult = mongoLockDao.incrByWithExpire(key, 1 , System.currentTimeMillis() + expire); //如果結(jié)果是1,代表當前請求獲得鎖 if ((Integer) mapResult.get( "value" ) == 1 ) { return true ; //如果結(jié)果>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實現(xiàn)代碼:
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的數(shù)據(jù) * * @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(原子加),并設(shè)置過期時間 * * @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; } /** * 根據(jù)value刪除過期的內(nèi)容 * * @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; } } |
四、設(shè)計思路
前提:利用mongo實現(xiàn)id自增,且自增過程為原子操作,即線程安全。
假設(shè)有A、B兩個請求通過請求資源。
當A請求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即1,此時結(jié)果等于1則表明,A請求過程中沒有其他請求請求到資源,將鎖資源分配給A。
當B請求到資源是調(diào)用mongo自增 +1,并將結(jié)果返回給A,即2。此時結(jié)果大于1則表明,B請求過程中有其他請求請求到資源,鎖資源不能分配給B。
這樣就是實現(xiàn)了多個請求請求同一個鎖并且排隊。
關(guān)于鎖過期時間 :
如果圖中代碼1releaseLockExpire(key, System.currentTimeMillis())修改為releaseLockExpire(key),即在釋放鎖的時候沒有傳入過期時間,會產(chǎn)生如下情況:
A、B兩個請求同時通過條件,進入到代碼 1
B執(zhí)行完刪除操作,進入代碼2,并且剛剛獲得到鎖資源,而此時A及有可能剛開始執(zhí)行釋放鎖的操作。
此時就會發(fā)生,A釋放了B剛剛獲得的鎖,這樣B就會失去剛剛獲得的鎖,而B確沒有感知,從而造成邏輯錯誤。
而releaseLockExpire(key, System.currentTimeMillis()),即在釋放鎖的時候判斷一下過期時間,這樣就不會誤刪B剛剛獲得的鎖。
以上這篇mongo分布式鎖Java實現(xiàn)方法(推薦)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。