mongo分佈式鎖Java實現

1、分佈式鎖使用場景:

  1. 代碼部署在多臺服務器上,即分佈式部署。
  2. 多個進程同步訪問一個共享資源。

2、須要的技術:

  1.  數據庫:mongo
  2. 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

 3、實現代碼:

  主實現邏輯及外部調用方法,得到鎖調用getLock,釋放鎖調用releaseLock,詳情以下:html

 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      * 一、首先判斷鎖是否被其餘請求得到;若是沒被其餘請求得到則往下進行;
17      * 二、判斷鎖資源是否過時,若是過時則釋放鎖資源;
18      * 3.一、嘗試得到鎖資源,若是value=1,那麼得到鎖資源正常;(在當前請求已經得到鎖的前提下,還可能有其餘請求嘗試去得到鎖,此時會致使當前鎖的過時時間被延長,因爲延長時間在毫秒級,能夠忽略。)
19      * 3.二、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實現代碼:java

 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實體:spring

 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

4、設計思路

前提:利用mongo實現id自增,且自增過程爲原子操做,即線程安全。mongodb

  1. 假設有A、B兩個請求同時請求資源。
  2. 當A請求到資源是調用mongo自增 +1,並將結果返回給A,即1。此時結果等於1則代表,A請求過程當中沒有其餘請求請求到資源,將鎖資源分配給A。
  3. 當B請求到資源是調用mongo自增 +1,並將結果返回給B,即2。此時結果大於1則代表,B請求過程當中有其餘請求請求到資源,鎖資源不能分配給B。
  4. 這樣就是實現了多個請求請求同一個鎖而且排隊。

關於鎖過時時間 :數據庫

          若是圖中代碼1releaseLockExpire(key, System.currentTimeMillis())修改成releaseLockExpire(key),即在釋放鎖的時候沒有傳入過時時間,會產生以下狀況:安全

  1. A、B兩個請求同時經過條件,進入到代碼 
  2. B執行完刪除操做,進入代碼2,而且剛剛得到到鎖資源,而此時A可能剛開始執行釋放鎖的操做。
  3. 此時就會發生,A釋放了B剛剛得到的鎖,這樣B就會失去剛剛得到的鎖,而B確沒有感知,從而形成邏輯錯誤。
  4. 而releaseLockExpire(key, System.currentTimeMillis()),即在釋放鎖的時候判斷一下過時時間,這樣就不會誤刪B剛剛得到的鎖。

 

 

致謝:感謝您的閱讀!轉載請加原文連接,謝謝!http://www.cnblogs.com/faaidong/p/7126716.html 服務器

相關文章
相關標籤/搜索