DCS實踐乾貨:使用Redis實現分佈式鎖

場景介紹html

不少互聯網場景(如商品秒殺,論壇回帖蓋樓等),須要用加鎖的方式,以對某種資源進行順序訪問控制。若是應用服務集羣部署,則涉及到對分佈式應用加鎖。當前分佈式加鎖主要有三種方式:(磁盤)數據庫、緩存數據庫、Zookeeper。接下里讓咱們一塊兒看看加鎖實踐過程。java

 

加鎖實現redis

1  package dcsDemo01;

2   

3  import java.util.UUID;

4   

5  import redis.clients.jedis.Jedis;

6   

7  public class DistributedLock {

8      private final String host = "192.168.0.220";

9      private final int port = 6379;

10   

11      private static final String SUCCESS = "OK";

12      private static final String SET_IF_NOT_EXIST = "NX";

13      private static final String EXPIRE_TIME = "PX";

14   

15      public  DistributedLock(){}

16   

17      /*

18       * @param lockName      鎖名

19       * @param timeout       獲取鎖的超時時間

20       * @param lockTimeout   鎖的有效時間

21       * @return              鎖的標識

22       */

23      public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) {

24          String ret = null;

25          Jedis jedisClient = new Jedis(host, port);

26   

27          try {

28              String authMsg = jedisClient.auth("Demo@123");

29              if (!SUCCESS.equals(authMsg)) {

30                  System.out.println("AUTH FAILED: " + authMsg);

31              }

32   

33              String identifier = UUID.randomUUID().toString();

34              String lockKey = "DLock:" + lockName;

35              long end = System.currentTimeMillis() + timeout;

36   

37              while(System.currentTimeMillis() < end) {

38                  String result = jedisClient.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_TIME, lockTimeout);

39                  if(SUCCESS.equals(result)) {

40                      ret = identifier;

41                      break;

42                  }

43   

44                  try {

45                      Thread.sleep(2);

46                  } catch (InterruptedException e) {

47                      Thread.currentThread().interrupt();

48                  }

49              }

50          }

51          catch (Exception e) {

52              e.printStackTrace();

53          }finally {

54              jedisClient.quit();

55              jedisClient.close();

56          }

57   

58          return ret;

59      }

60   

61      /*

62       * @param lockName        鎖名

63       * @param identifier    鎖的標識

64       */

65      public void releaseLock(String lockName, String identifier) {

66          Jedis jedisClient = new Jedis(host, port);

67   

68          try {

69              String authMsg = jedisClient.auth("Demo@123");

70              if (!SUCCESS.equals(authMsg)) {

71                  System.out.println("AUTH FAILED: " + authMsg);

72              }

73   

74              String lockKey = "DLock:" + lockName;

75              if(identifier.equals(jedisClient.get(lockKey))) {

76                  jedisClient.del(lockKey);

77              }

78          }

79          catch (Exception e) {

80              e.printStackTrace();

81          }finally {

82              jedisClient.quit();

83              jedisClient.close();

84          }

85      }

86  }

 

測試代碼數據庫

假設20個線程對10臺mate10手機進行搶購:緩存

package dcsDemo01;

import java.util.UUID;

 

public class CaseTest {

    public static void main(String[] args) {

        ServiceOrder service = new ServiceOrder();

        for (int i = 0; i < 20; i++) {

            ThreadBuy client = new ThreadBuy(service);

            client.start();

        }

    }

}

 

class ServiceOrder {

    private final int MAX = 10;

 

    DistributedLock DLock = new DistributedLock();

 

    int n = 10;

 

    public void handleOder() {

        String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();

        String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);

        System.out.println("正在爲用戶:" + userName + " 處理訂單");

        if(n > 0) {

            int num = MAX - n + 1;

            System.out.println("用戶:"+ userName + "購買第" + num + "臺,剩餘" + (--n) + "臺");

        }else {

            System.out.println("用戶:"+ userName + "沒法購買,已售罄!");

        }

        DLock.releaseLock("Huawei Mate 10", identifier);

    }

}

 

class ThreadBuy extends Thread {

    private ServiceOrder service;

 

    public ThreadBuy(ServiceOrder service) {

        this.service = service;

    }

 

    @Override

    public void run() {

        service.handleOder();

    }

}

 

運行結果dom

配置好實際的緩存實例鏈接地址、端口與鏈接密碼,運行代碼,獲得如下結果:分佈式

正在爲用戶:eee56fb7Thread-16 處理訂單

用戶:eee56fb7Thread-16購買第1臺,剩餘9臺

正在爲用戶:d6521816Thread-2 處理訂單

用戶:d6521816Thread-2購買第2臺,剩餘8臺

正在爲用戶:d7b3b983Thread-19 處理訂單

用戶:d7b3b983Thread-19購買第3臺,剩餘7臺

正在爲用戶:36a6b97aThread-15 處理訂單

用戶:36a6b97aThread-15購買第4臺,剩餘6臺

正在爲用戶:9a973456Thread-1 處理訂單

用戶:9a973456Thread-1購買第5臺,剩餘5臺

正在爲用戶:03f1de9aThread-14 處理訂單

用戶:03f1de9aThread-14購買第6臺,剩餘4臺

正在爲用戶:2c315ee6Thread-11 處理訂單

用戶:2c315ee6Thread-11購買第7臺,剩餘3臺

正在爲用戶:2b03b7c0Thread-12 處理訂單

用戶:2b03b7c0Thread-12購買第8臺,剩餘2臺

正在爲用戶:75f25749Thread-0 處理訂單

用戶:75f25749Thread-0購買第9臺,剩餘1臺

正在爲用戶:26c71db5Thread-18 處理訂單

用戶:26c71db5Thread-18購買第10臺,剩餘0臺

正在爲用戶:c32654dbThread-17 處理訂單

用戶:c32654dbThread-17沒法購買,已售罄!

正在爲用戶:df94370aThread-7 處理訂單

用戶:df94370aThread-7沒法購買,已售罄!

正在爲用戶:0af94cddThread-5 處理訂單

用戶:0af94cddThread-5沒法購買,已售罄!

正在爲用戶:e52428a4Thread-13 處理訂單

用戶:e52428a4Thread-13沒法購買,已售罄!

正在爲用戶:46f91208Thread-10 處理訂單

用戶:46f91208Thread-10沒法購買,已售罄!

正在爲用戶:e0ca87bbThread-9 處理訂單

用戶:e0ca87bbThread-9沒法購買,已售罄!

正在爲用戶:f385af9aThread-8 處理訂單

用戶:f385af9aThread-8沒法購買,已售罄!

正在爲用戶:46c5f498Thread-6 處理訂單

用戶:46c5f498Thread-6沒法購買,已售罄!

正在爲用戶:935e0f50Thread-3 處理訂單

用戶:935e0f50Thread-3沒法購買,已售罄!

正在爲用戶:d3eaae29Thread-4 處理訂單

用戶:d3eaae29Thread-4沒法購買,已售罄!

 

不加鎖場景、

若是註釋掉加鎖代碼,變成無鎖狀況,則搶購無序。

//測試類中註釋兩行用於加鎖的代碼:
public void handleOder() {
    String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();
    //加鎖代碼
    //String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);
    System.out.println("正在爲用戶:" + userName + " 處理訂單");
    if(n > 0) {
        int num = MAX - n + 1;
        System.out.println("用戶:"+ userName + "夠買第" + num + "臺,剩餘" + (--n) + "臺");
    }else {
        System.out.println("用戶:"+ userName + "沒法夠買,已售罄!");
    }
    //加鎖代碼
    //DLock.releaseLock("Huawei Mate 10", identifier);
}
 

註釋加鎖代碼後的運行結果,能夠看出處理過程是無序的:

正在爲用戶:e04934ddThread-5 處理訂單
正在爲用戶:a4554180Thread-0 處理訂單
用戶:a4554180Thread-0購買第2臺,剩餘8臺
正在爲用戶:b58eb811Thread-10 處理訂單
用戶:b58eb811Thread-10購買第3臺,剩餘7臺
正在爲用戶:e8391c0eThread-19 處理訂單
正在爲用戶:21fd133aThread-13 處理訂單
正在爲用戶:1dd04ff4Thread-6 處理訂單
用戶:1dd04ff4Thread-6購買第6臺,剩餘4臺
正在爲用戶:e5977112Thread-3 處理訂單
正在爲用戶:4d7a8a2bThread-4 處理訂單
用戶:e5977112Thread-3購買第7臺,剩餘3臺
正在爲用戶:18967410Thread-15 處理訂單
用戶:18967410Thread-15購買第9臺,剩餘1臺
正在爲用戶:e4f51568Thread-14 處理訂單
用戶:21fd133aThread-13購買第5臺,剩餘5臺
用戶:e8391c0eThread-19購買第4臺,剩餘6臺
正在爲用戶:d895d3f1Thread-12 處理訂單
用戶:d895d3f1Thread-12沒法購買,已售罄!
正在爲用戶:7b8d2526Thread-11 處理訂單
用戶:7b8d2526Thread-11沒法購買,已售罄!
正在爲用戶:d7ca1779Thread-8 處理訂單
用戶:d7ca1779Thread-8沒法購買,已售罄!
正在爲用戶:74fca0ecThread-1 處理訂單
用戶:74fca0ecThread-1沒法購買,已售罄!
用戶:e04934ddThread-5購買第1臺,剩餘9臺
用戶:e4f51568Thread-14購買第10臺,剩餘0臺
正在爲用戶:aae76a83Thread-7 處理訂單
用戶:aae76a83Thread-7沒法購買,已售罄!
正在爲用戶:c638d2cfThread-2 處理訂單
用戶:c638d2cfThread-2沒法購買,已售罄!
正在爲用戶:2de29a4eThread-17 處理訂單
用戶:2de29a4eThread-17沒法購買,已售罄!
正在爲用戶:40a46ba0Thread-18 處理訂單
用戶:40a46ba0Thread-18沒法購買,已售罄!
正在爲用戶:211fd9c7Thread-9 處理訂單
用戶:211fd9c7Thread-9沒法購買,已售罄!
正在爲用戶:911b83fcThread-16 處理訂單
用戶:911b83fcThread-16沒法購買,已售罄!
用戶:4d7a8a2bThread-4購買第8臺,剩餘2臺

 

總的來講,使用DCS服務中Redis類型的緩存實例實現分佈式加鎖,有幾大優點:ide

一、加鎖操做簡單,使用SET、GET、DEL等幾條簡單命令便可實現鎖的獲取和釋放。性能

二、性能優越,緩存數據的讀寫優於磁盤數據庫與Zookeeper。測試

三、可靠性強,DCS有主備和集羣實例類型,避免單點故障。

 

以上代碼實現僅展現使用DCS服務進行加鎖訪問的便捷性,具體技術實現須要考慮死鎖、鎖的檢查等狀況,歡迎點擊分佈式緩存服務DCS瞭解更多。

相關文章
相關標籤/搜索