项目总结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);
    }
}

 

posted on 2020-03-20 16:55  我不吃番茄  阅读(3336)  评论(0编辑  收藏  举报