Redis鎖相關

Redis鎖相關

 

    君不見,高堂明鏡悲白髮,朝如青絲暮成雪。

 

背景:面試的時候被問到有哪些鎖,很快脫口而出Volatile、Synchronized和ReentrantLock,也能講出他們之間的一些區別;當問到如在同一服務下同步鎖可以起作用,但是在不同的服務器上部署同一個微服務工程,然後用nginx作代理,很明顯,現在的線程鎖不管用了。分佈式情況下如何保證線程同步?當時就答不上來了,分佈式環境下我們需要換一把鎖,這把鎖必須和兩套甚至多套系統沒有任何的耦合度。可以使用Redis鎖實現分佈式場景下的線程同步,使用Redies的API,如果key不存在,則設置一個key,這個key就是我們現在使用的一把鎖。每個線程到此處,先設置鎖,如果設置鎖失敗,則表明當前有線程獲取到了鎖,就返回。

一、單一服務器下的鎖

例如將商品的數量存到Redis中。每個用戶搶購前都需要到Redis中查詢商品數量(代替mysql數據庫。不考慮事務),如果商品數量大於0,則證明商品有庫存;然後我們在進行庫存扣減和接下來的操作;因爲多線程併發問題,我們還需要在get()方法內部使用同步代碼塊,保證查詢庫存和減庫存操作的原子性。

 1 import lombok.AllArgsConstructor;
 2 import lombok.extern.slf4j.Slf4j;
 3 import org.springframework.data.redis.core.RedisTemplate;
 4 import org.springframework.web.bind.annotation.GetMapping;
 5 import org.springframework.web.bind.annotation.RequestHeader;
 6 import org.springframework.web.bind.annotation.RequestMapping;
 7 import org.springframework.web.bind.annotation.RestController;
 8 
 9 @RestController
10 @AllArgsConstructor
11 @RequestMapping("/redis")
12 @Slf4j
13 public class TryRedisLock {
14 
15     private RedisTemplate<String, String> redisTemplate;
16 
17     @GetMapping(value = "/try/buy")
18     public String get(@RequestHeader(required = false) String userId) {
19         synchronized (this) {  // 單機同步
20             String bird = redisTemplate.opsForValue().get("bird");
21             Integer count = Integer.valueOf(bird);
22             if (count > 0) {
23                 // 減庫存
24                 redisTemplate.opsForValue().set("bird", String.valueOf(count - 1));
25                 log.info("用戶{}, 搶到了, {} 號商品!", userId, bird);
26             }
27             return "零庫存";
28         }
29     }
30 }
View Code

二、分佈式場景redis鎖

分佈式場景下使用redis鎖需要注意如下幾個問題:

  • 一臺服務器宕機,導致無法釋放鎖;可以在try-catch的finally中釋放鎖或者給每一把鎖加過期時間。
  • 任務線程未執行完畢但鎖已失效;可以延長鎖的過期時間,使用定時器防止key過期。
  • 使用Redisson框架簡化代碼;getLock()方法代替了Redis的setIfAbsent()lock()設置過期時間,最終我們在交易結束後釋放鎖;延長鎖的操作則由Redisson框架替我們完成,它會使用輪詢去查看key是否過期,在交易沒有完成時,自動重設Redis的key過期時間。
 1 import lombok.AllArgsConstructor;
 2 import lombok.extern.slf4j.Slf4j;
 3 import org.springframework.data.redis.core.RedisTemplate;
 4 import org.springframework.web.bind.annotation.GetMapping;
 5 import org.springframework.web.bind.annotation.RequestHeader;
 6 import org.springframework.web.bind.annotation.RequestMapping;
 7 import org.springframework.web.bind.annotation.RestController;
 8 
 9 @RestController
10 @AllArgsConstructor
11 @RequestMapping("/redis")
12 @Slf4j
13 public class TryRedisLock {
14 
15     private RedisTemplate<String, String> redisTemplate;
16 
17 
18     @GetMapping(value = "/try/again/buy")
19     public String getJudge(@RequestHeader(required = false) String userId) {
20         // 每個線程到此處,先設置鎖
21         /**
22          * 使用Redies的API如果key不存在,則設置一個key。這個key就是我們現在使用的一把鎖
23          * 每個線程到此處,先設置鎖
24          * 如果設置鎖失敗,則表明當前有線程獲取到了鎖,就返回。
25          */
26         Boolean birdLock = redisTemplate.opsForValue().setIfAbsent("birdLock", "");
27         if (!birdLock) {
28             return "";
29         }
30         try {
31             String bird = redisTemplate.opsForValue().get("bird");
32             Integer count = Integer.valueOf(bird);
33             if (count > 0) {
34                 redisTemplate.opsForValue().set("bird", String.valueOf(count - 1));
35                 log.info("用戶{}, 搶到了, {} 號商品!", userId, bird);
36             }
37         } finally {
38             redisTemplate.delete("birdLock");  // 刪除鎖
39         }
40         return "";
41     }
42 
43     @GetMapping(value = "/try/buy")
44     public String get(@RequestHeader(required = false) String userId) {
45         synchronized (this) {  // 單機同步
46             String bird = redisTemplate.opsForValue().get("bird");
47             Integer count = Integer.valueOf(bird);
48             if (count > 0) {
49                 // 減庫存
50                 redisTemplate.opsForValue().set("bird", String.valueOf(count - 1));
51                 log.info("用戶{}, 搶到了, {} 號商品!", userId, bird);
52             }
53             return "零庫存";
54         }
55     }
56 }
View Code

 

 

 

 

君不見

         高堂明鏡悲白髮

          朝如青絲暮成雪

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章