MiniMall:使用Redis實現分佈式鎖,太簡單不過了

1. 什麼是鎖

在一個進程中,當存在多個線程同時操作某個共享資源時,就需要對共享資源做同步,使其在修改這個共享資源時能夠線性地執行操作。而實現同步的手段就是鎖,當線程準備對共享資源做修改前,先獲取鎖,如果當前共享資源已經被鎖,則進行等待;若沒有被鎖,則成功獲取鎖,並允許執行修改。在修改完畢後,釋放鎖資源,那麼下一個線程也是如此,只有獲取到鎖,才能對共享資源修改。

2. 什麼是分佈式鎖

分佈式鎖是爲了防止分佈式系統中多個進程之間相互干擾,從而需要一種分佈式協調技術來對這些進程進行調度。

3. Redis實現分佈式鎖

實現分佈式鎖的手段有很多,比如基於數據庫的悲觀鎖和樂觀鎖、基於Redis做分佈式鎖、基於Zookeeper做分佈式鎖等等。之前,我們把Redis當做緩存系統使用,今天我們再來看看如何使用Redis實現分佈式鎖。

Redis實現分佈式鎖需要用到其中的兩個方法:setnx()expire()

  • setnx()

setnx的含義就是SET if Not Exists,該方法是原子的,如果key不存在,則設置當前key成功,返回1;如果當前key已經存在,則設置當前key失敗,返回0。

  • expire()

expire設置過期時間,要注意的是setnx命令不能設置key的超時時間,只能通過expire來對key設置。

3.1 實現步驟

  1. setnx(key, value)如果返回0,則說明當前資源已經被鎖,需要等待;如果返回1,則說明成功獲取鎖;
  2. expire()命令對key設置超時時間,避免死鎖;
  3. 執行業務代碼後,通過delete命令刪除key。

需要注意的是:從解決日常工作中的需求來說,該方案已經夠用。但從技術角度來說,該方案還不夠嚴謹。比如在第一步設置setnx執行成功後,expire命令執行前出現了異常,那麼就還是會出現死鎖問題。但是我們這個項目中暫時不考慮這種異常情況了,在後面可能會有一個專門討論分佈式鎖的專題博客,可以持續關注下。

3.2 業務背景

以商品微服務下的商品入庫單業務爲背景,商品入庫單存在未生效已生效兩種業務狀態,當錄入一個商品入庫單保存成功之後,該商品入庫單爲未生效狀態,此時商品還沒有入庫。只有在執行生效這個業務動作時,商品入庫單從未生效變成已生效狀態,然後再對商品的庫存進行增加。從業務角度上來說,一個商品入庫單是不允許重複生效的,所以在這裏就需要對生效的商品入庫單進行加鎖,避免分佈式環境下重複生效。

3.3 代碼實現

com.autumn.mall.commons.utils包結構中,有一個RedisUtils工具類,用來對Redis常規操作做了簡單的封裝,其中我們也對獲取分佈式鎖的操作進行了封裝:

public boolean tryLock(String key) {
    return tryLock(key, null);
}
public boolean tryLock(String key, String value) {
    return tryLock(key, value, 3, TimeUnit.MINUTES);
}
public boolean tryLock(String key, String value, int timeout, TimeUnit timeUnit) {
    try {
        if (StringUtils.isBlank(value)) {
            long currTime = System.currentTimeMillis();
            // 加鎖成功
            return redisTemplate.opsForValue().setIfAbsent(key, currTime);
        }
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    } finally {
        redisTemplate.expire(key, timeout, timeUnit);
    }
}

商品入庫單(GoodsInboundServiceImpl)生效方法:

public void doEffect(String uuid) {
    // 獲取分佈式鎖
    try {
        while (redisUtils.tryLock(getLockKeyPrefix() + uuid) == false) {
            TimeUnit.SECONDS.sleep(3);
        }
    } catch (Exception e) {
        MallExceptionCast.cast(CommonsResultCode.TRY_LOCKED_ERROR);
    }
    Optional<GoodsInbound> optional = goodsInboundRepository.findById(uuid);
    if (optional.isPresent() == false) {
        MallExceptionCast.cast(CommonsResultCode.ENTITY_IS_NOT_EXIST);
    }
    if (optional.get().getState().equals(BizState.effect)) {
        MallExceptionCast.cast(ProductResultCode.ENTITY_IS_EQUALS_TARGET_STATE);
    }
    // 生效入庫單
    GoodsInbound entity = optional.get();
    entity.setState(BizState.effect);
    getRepository().save(entity);
    // 商品入庫
    List<GoodsInboundDetail> details = goodsInboundDetailRepository.findAllByGoodsInboundUuidOrderByLineNumber(entity.getUuid());
    List<Stock> stocks = new ArrayList<>();
    details.stream().forEach(detail -> {
        Stock stock = new Stock();
        stock.setEntityKey(MallModuleKeyPrefixes.PRODUCT_KEY_PREFIX_OF_GOODS + detail.getGoodsUuid());
        stock.setWarehouse(entity.getWarehouse());
        stock.setQuantity(detail.getQuantity());
        stocks.add(stock);
    });
    ResponseResult responseResult = stockClient.inbound(stocks);
    // 刪除分佈式鎖
    redisUtils.remove(getLockKeyPrefix() + uuid);
}
——End——
更多精彩分享,可掃碼關注微信公衆號哦。

在這裏插入圖片描述

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