• 项目总结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);
        }
    }
  • 相关阅读:
    js 实现继承的6种方式(逐渐优化)
    http2.0 特性
    http 206请求
    http put post请求区别
    stopPropagation 和stopImmediatePropagation区别
    JavaScript事件流
    BFC特性 形成BFC
    元素高度、宽度获取 style currentStyle getComputedStyle getBoundingClientRect
    三栏布局解决方案
    jquery vue 框架区别
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/12532783.html
Copyright © 2020-2023  润新知