Redis通过lua脚本模拟一个商品秒杀

1.使用场景

       公司2周年准备上一个秒杀,针对商品A,价值1W,准备在上午10点整,进行一次秒杀,一共20个库存。

预测当日会有100W用户进行抢购

 

2.遇到的问题

      高并发、库存不能超卖、不能查询数据库 否则数据库抵挡不住压力

 

3.实现方案

      1.使用 redis 保存四件商品的库存数量

测试:0>set seckillGoods:1001 20
"OK"

 

  2.利用lua脚本进行减库存,首先编写好lua脚本

 网上有很多案例,lua脚本如果直接复制,都会存在问题(少分号、少空格、引号错误、忘记转数字等),这里已解决。

  如果只想要脚本则复制以下代码即可

local isExist = redis.call('exists', KEYS[1]);
if (tonumber(isExist) > 0) then
	local goodsNumber = redis.call('get', KEYS[1]);
	if (tonumber(goodsNumber) > 0) then
		redis.call('decr',KEYS[1]);
		return 1;
	else
		redis.call('del', KEYS[1]);
		return 0;
		end;
else
return -1;
end;

 

  3.编写测试案例

@Component
public class LuaReduceStock {

    private static final String STOCK_LUA;

    @Resource
    private RedisTemplate redisTemplate;

    static {
        StringBuilder s = new StringBuilder();
        s.append("local isExist = redis.call('exists', KEYS[1]); ");
        s.append("if (tonumber(isExist) > 0) then ");
        s.append("      local goodsNumber = redis.call('get', KEYS[1]);  ");
        s.append("      if (tonumber(goodsNumber) > 0) then ");
        s.append("          redis.call('decr',KEYS[1]);   ");
        s.append("          return 1;   ");
        s.append("      else "
                          +"redis.call('del', KEYS[1]);    ");
        s.append("          return 0; ");
        s.append("          end; ");
        s.append("else ");
        s.append("      return -1; ");
        s.append("      end;");
        STOCK_LUA = s.toString();
    }

/**
 * 减库存
 * @param key
 * @return
 */
    public boolean reduceStock(String key){
        List<String> keys = new ArrayList<>();
        keys.add(key);

        List<String> args = new ArrayList<>();

        Long result  = (Long) redisTemplate.execute(new RedisCallback() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                Object eval = ((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);
                return Long.valueOf(eval.toString());
            }
        });
        return result  > 0;
    }
}

  

@Resource
private LuaReduceStock luaReduceStock;

@RequestMapping("/api/v4/testLuaReduceStock")
public JsonResult testLuaReduceStock(String key){
    List<Thread> allThread = new ArrayList<>();

    for(int i = 1; i<= 1000; i++){
        Thread thread = new Thread(()-> {
            boolean b = luaReduceStock.reduceStock(key);
            if(b){
                System.out.println("恭喜您,抢到了库存!!!" + Thread.currentThread().getName());
                //MQ.sendSuccessMessage();
                最终抢到库存的用户,可以发送一条消息到队列中,进行异步下单。
            }else{
                System.out.println("对不起,库存已卖光啦!!!" + Thread.currentThread().getName());
            }
        },"线程" + i);
        allThread.add(thread);
    }
    for(Thread thread : allThread){
        thread.start();
    }
    return JsonResult.buildSuccessResult("成功");
}

 

最终看控制台打印的效果可知,只有20个线程抢到了库存。

 

 

 

4.总结lua脚本调用redis的优势

  1. 多个命令合并到脚本统一处理,减少多个命令的网络开销
  2. 脚本能确保操作的原子性,不会受到其他客户端命令的影响
  3. 代码复用,redis将永久存放客户端发送的脚本

版权声明:本文为CSDN19970806原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。