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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/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

君不見
高堂明鏡悲白發
朝如青絲暮成雪
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/300110.html
標籤:其他

