基于redis的分布式鎖

什么是分布式鎖

為了防止分布式系統(tǒng)中的多個進程之間相互干擾,我們需要一種分布式協(xié)調(diào)技術(shù)來對這些進程進行調(diào)度。而這個分布式協(xié)調(diào)技術(shù)的核心就是來實現(xiàn)這個分布式鎖。

分布式鎖應該具備哪些條件

  • 在分布式系統(tǒng)環(huán)境下,一個方法在同一時間只能被一個機器的一個線程執(zhí)行
  • 高可用的獲取鎖與釋放鎖
  • 高性能的獲取鎖與釋放鎖
  • 具備可重入特性(可理解為重新進入,由多于一個任務并發(fā)使用,而不必擔心數(shù)據(jù)錯誤)
  • 具備鎖失效機制,防止死鎖
  • 具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗

試驗高并發(fā)場景

  @RestController
@RequestMapping("/order")
@AllArgsConstructor
public class OrderController {

    private static ReentrantLock lock = new ReentrantLock();

    private StringRedisTemplate redisTemplate;

    @GetMapping("/reduce_order")
    public String executor() {
        reduceOrder();
        return "end";
    }

    public void reduceOrder() {
        String productKey = "order";
        try {
                lock.lock();
            int order = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(productKey)));
            if (order > 0) {
                int realorder = order - 1;
                redisTemplate.opsForValue().set(productKey, String.valueOf(realorder));
                System.out.println("訂單數(shù)-1,剩余訂單數(shù)量:" + realorder);
            } else {
                System.out.println("庫存數(shù)不足,請及時補充訂單");
            }
        } catch (Exception e) {
            System.out.println("訂單扣除失敗");
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

上述代碼只是簡單一個模擬扣除訂單的方法,如果多個進程同時運行時候就會出現(xiàn)訂單重復扣減的問題,redis中設(shè)置了訂單數(shù)為50

使用jmeter測試

1.我們使用nginx

做轉(zhuǎn)發(fā)項目8081和8082端口

  • 啟動項目8081和8082端口


    image
  • 使用nginx 做轉(zhuǎn)發(fā)8081和8082端口

redis配置

user  nginx;
worker_processes  1;

events {
  worker_connections  1024;
}

http {
  include       mime.types;
  default_type  application/octet-stream;

  sendfile        on;

  keepalive_timeout  65;

  upstream myapp1 {
     # 換成主機ip
      server 10.8.162.22:8081 weight=10;
      server 10.8.162.22:8082 weight=10;
  }

  server {
      listen 80;
      location / {
          proxy_pass http://myapp1;
          index index.jsp index.html index.htm;
      }
  }
}

2.下載jmeter

3.啟動并配置jmeter


啟動jmeter

添加線程組

添加Http請求用例

配置jmeter

4.運行jmeter,查看結(jié)果


執(zhí)行
8081端口
8082端口

從結(jié)果中很明顯看出進程1-8081 和進程2-8082明顯重復扣除訂單

分布式鎖就是防止這種情況發(fā)生

redis分布式鎖的原理

當一個線程執(zhí)行 setnx 返回 1,說明 key 原本不存在,該線程成功得到了鎖;當一個線程執(zhí)行 setnx 返回 0,說明 key 已經(jīng)存在,該線程搶鎖失敗。

1.redis核心要素

  • 加鎖 setnx

(1 成功獲取鎖 0 已有鎖 加鎖失?。?/code>

  • 解鎖

直接刪除即可

  • 鎖超時

(expire設(shè)置超時時間)

2.分布鎖問題

  • 如何保證原子性

只設(shè)置key值還沒在設(shè)置超時時間的時候掛了 (redis lua腳本[同時成功,同時失敗])

  • 誤刪鎖

(給每個鎖相應的id)

  • 業(yè)務執(zhí)行時間大于鎖的超時間,業(yè)務沒執(zhí)行完,鎖消失了(續(xù)命)

守護線程,鎖快超時的時候重制超時時間while循環(huán)

代碼如下

    public void reduceOrder() {
        String localKey = "product_001";
        String productKey = "order";
        String clientId = UUID.randomUUID().toString(); // 鎖id
        try {
            lock.lock();
            //redis 加鎖 setnx  設(shè)置超時時間
            Boolean result = redisTemplate.opsForValue().setIfAbsent(localKey, clientId, 10, TimeUnit.SECONDS);

            if (!result) { //true               System.out.println("正在搶貨中.....");
                return;
            }

            int order = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(productKey)));
            if (order > 0) {
                int realorder = order - 1;
                redisTemplate.opsForValue().set(productKey, String.valueOf(realorder));
                System.out.println("訂單數(shù)-1,剩余訂單數(shù)量:" + realorder);
            } else {
                System.out.println("庫存數(shù)不足,請及時補充訂單");
            }
        } catch (Exception e) {
            System.out.println("訂單扣除失敗");
            e.printStackTrace();
        } finally {
            //解鎖 判斷是否對應id 解決誤刪鎖
            //在判斷的時候可能掛了  應使用lua腳本保證原子行
            if (clientId.equals(redisTemplate.opsForValue().get(localKey))) {
                redisTemplate.delete(localKey);
            }
//            System.out.println("解鎖成功");
            lock.unlock();
        }
    }

//    }
}

上述代碼中沒有體現(xiàn)到lua腳本和守護線程(還有可重入鎖的問題),后面會直接使用Redission

重新執(zhí)行jmeter進行測試 測試結(jié)果

8081
8082

合計50個訂單,沒有重復扣除

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容