項目總結59:Redis分布式鎖解決電商訂單庫存並發問題


項目總結59:Redis分布式鎖解決電商訂單庫存並發問題

 

在電商分布式項目中,需要考慮提交訂單時,因為並發的原因導致庫存異常的情況。

其中一個解決方案是:使用redis鎖,因為Redis是單線程的,即線程安全的;在提交訂單的時候,先通過Redis鎖進行庫存判斷,如果庫存校驗通過,則正常提交頂頂那,否則返回失敗。

具體邏輯如下:

  1- 用戶請求提交訂單接口,接口內先通過Redis鎖進行庫存校驗(如果第一次獲取鎖失敗,則會繼續請求鎖,但不超過5次);

  2- Redis鎖進行庫存校驗,從訂單層面具有排他性(即一個訂單在進行Redis鎖庫存校驗時),其它提交的訂單只能等待。

  3- 且Redis鎖進行庫存校驗,做兩件事:(1)進行Redis庫存校驗,如果庫存不夠,則返回false;否則繼續(2);(2)進行Redis減庫存操作。

  4-Redis鎖進行庫存校驗通過后,訂單信息被正常提交。

具體代碼如下

 

    @Autowired
    private CommonRedisHelper commonRedisHelper;


    public final static  String PREFIX_LOCK_ORDER_SUBMIT = "lock_orderSubmit";
    
    //校驗並更新庫存
    public Boolean updateStockToRedis(List<Long> cartIdList){
        boolean lock = commonRedisHelper.lock(PREFIX_LOCK_ORDER_SUBMIT);
        if(lock){
            //更新redis中sku的庫存
            //代碼略... reduceMultiSkuStock(cartIdList)

            //刪除鎖
            commonRedisHelper.delete(PREFIX_LOCK_ORDER_SUBMIT);
            return true;
        }else{
            // 設置失敗次數計數器, 當到達5次時, 返回失敗
            int failCount = 1;
            while(failCount <= 5){
                // 等待100ms重試
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (commonRedisHelper.lock(PREFIX_LOCK_ORDER_SUBMIT)){
                    // 執行邏輯操作
                    //更新redis中sku的庫存
                    //代碼略... reduceMultiSkuStock(cartIdList)

                    //刪除鎖
                    commonRedisHelper.delete(PREFIX_LOCK_ORDER_SUBMIT);

                    return true;
                }else{
                    failCount ++;
                }
            }
            return false;
        }
    }

    //判斷並更新某個SKU庫存
    private boolean reduceMultiSkuStock(List<Long> cartIdList){
        //2-判斷秒殺商品SKU是否足夠
        //代碼略...
        //2-更新秒殺商品的庫存
        //代碼略...
    }

 

 

 

 CommonRedisHelper 類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Component
public class CommonRedisHelper {
    //鎖名稱
    public static final String LOCK_PREFIX = "redis_lock";
    //加鎖失效時間,毫秒
    public static final int LOCK_EXPIRE = 300; // ms

    @Autowired
    RedisTemplate redisTemplate;


    /**
     *  最終加強分布式鎖
     *
     * @param key key值
     * @return 是否獲取到
     */
    public boolean lock(String key){
        String lock = LOCK_PREFIX + key;
        // 利用lambda表達式
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            //當前鎖的過期時間
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            //當鎖不存在時,設置鎖,key為鎖名稱,value為過期時間
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());

            if (acquire) {
                //如果設置成功,則返回true
                return true;
            } else {
                //如果鎖沒有設置成功
                //獲取已經存在的鎖的value(即已經存在的鎖的過期時間)
                byte[] value = connection.get(lock.getBytes());

                //當已經存在的舊鎖的過期時間存在時
                if (Objects.nonNull(value) && value.length > 0) {
                    long expireTime = Long.parseLong(new String(value));
                    // 如果舊鎖已經過期,則重新加鎖
                    if (expireTime < System.currentTimeMillis()) {
                        // 重新強制加鎖,防止死鎖,並返回舊鎖的過期時間
                        byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
                        //判斷:如果舊鎖已經過期,則返回true,否則返回false
                        return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }
    /**
     * 刪除鎖
     *
     * @param key
     */
    public void delete(String key) {
        String lock = LOCK_PREFIX + key;
        redisTemplate.delete(lock);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM