zoukankan      html  css  js  c++  java
  • 使用Lua脚本通过原子减防止超卖

    需求

      双十二要搞一个一分钱门票抢购的活动。

    分析

      性能分析,抢购时会发生高并发,如果仅仅依靠Mysql数据库,有可能因为大量的请求频繁访问数据库造成服务器雪崩,所以考虑通过Redis减库存,最终的数据落地到DB中。

      在高并发的情况下,还要考虑到超卖的问题,因而打算使用Lua脚本完成原子减的操作。

      在这里,我们只针对减库存的操作进行分析。

    实现

      不使用原子操作,出现超卖的情况。第一步:先从redis中查出库存进行判断,第二步:如果库存>0,则进行减库存的操作。

      代码实现:

     1         // 第一步:从redis中查出库存
     2         Integer stock = (Integer) RedisUtils.get("stock");
     3 
     4         // 第二步:如果库存>0,则进行减库存的操作
     5         if (stock > 0) {
     6             long spareStock = RedisUtils.decr("stock", 1);
     7             System.out.println(getName() + "抢到了第" + spareStock + "件");
     8         } else {
     9             System.out.println("库存不足");
    10         }

      用多线程模拟并发请求:库存为500,创建505个线程去抢购。

    1         for(int i =1;i<=505;i++){
    2             MyThread2 thread =new MyThread2("线程"+i);
    3             thread.start();
    4         }

      执行结果:出现超卖问题,原因是:查询库存及减库存不是原子性操作。

       使用原子性操作:直接减库存。

    1     public void run() {
    2         long stock = RedisUtils.stock("stock");
    3         if (stock > 0) {
    4             System.out.println(getName() + "抢到了第" + stock + "件");
    5         } else {
    6             System.out.println("库存不足");
    7         }
    8 
    9     }

      Lua脚本实现减库存操作:

     /**
         * 库存不足
         */
        public static final int LOW_STOCK = 0;
        /**
         * 不限库存
         */
        public static final long UNINITIALIZED_STOCK = -1L;
    
        /**
         * 执行扣库存的脚本
         */
        public static final String STOCK_LUA;
    
        static {
            // 初始化减库存lua脚本
            StringBuilder sb = new StringBuilder();
            sb.append("if (redis.call('exists', KEYS[1]) == 1) then");
            sb.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
            sb.append("    if (stock == -1) then");
            sb.append("        return 1;");
            sb.append("    end;");
            sb.append("    if (stock > 0) then");
            sb.append("        redis.call('incrby', KEYS[1], -1);");
            sb.append("        return stock;");
            sb.append("    end;");
            sb.append("    return 0;");
            sb.append("end;");
            sb.append("return -1;");
    
            STOCK_LUA = sb.toString();
        }
    
        /**
         * 扣库存
         *
         * @param key 库存key
         * @return 扣减之前剩余的库存【0:库存不足; -1:库存未初始化; 大于0:扣减库存之前的剩余库存】
         */
        public static Long stock(String key) {
            // 脚本里的KEYS参数
            List<String> keys = new ArrayList<>();
            keys.add(key);
            // 脚本里的ARGV参数
            List<String> args = new ArrayList<>();
    
            Long result = (Long)redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                    // 集群模式
                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(STOCK_LUA, keys, args);
                    }
    
                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {
                        return (Long)((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);
                    }
                    return UNINITIALIZED_STOCK;
                }
            });
            return result;
        }

      执行结果:505个线程去抢500个商品,有五个线程会抢不到,测试结果与预期一致,解决了超卖的问题。

    参考:https://blog.csdn.net/xiaolyuh123/article/details/79208959

  • 相关阅读:
    win8应用的编码UI测试
    什么是Peer Review
    Android开发环境的搭建
    运用int.parse()判断闰年代码实现
    等价类划分方法的应用之EditBox(二)
    等价类划分方法的应用之EditBox
    集成测试
    数据可视化简介
    关于processing
    白盒测试VS黑盒测试
  • 原文地址:https://www.cnblogs.com/maguanyue/p/11972163.html
Copyright © 2011-2022 走看看