通过redis+lua实现加减库存

1. 场景

下单后库存校验或者秒杀场景下,有很多利用“锁”的方案来解决问题。但是加锁其实是一件性价比很低的事,所以我们采用用redis+lua的方式来实现这个功能。

2. 思路

阶段一
在库存加减逻辑中分为2个步骤:STEP1.读取库存STEP2.读取库存
利用其他方法例如"锁"等,也就是想控制好STEP2一定要紧跟STEP1,本质上就是确保获取的库存的最新的数据为最新。
阶段二
在相对较高的并发场景下,redis被常用作库存管理,我们需要通过最小成本的改动来实现库存的限制。但是redis的读取库存+读取库存一般都是有上层应用代码控制,有没有办法在一个函数调用中能串行执行这俩个步骤了?
阶段三
众所周知,redis是单线程的,并且现在已经支持lua脚本,那是不是可以利用该组合实现我们的场景了?

3. 方案

这个方案就很简单了,直接利用redistemplate执行lua脚本

4. code

4.1 lua脚本代码样例

    private static final String GET_COUPON_CODE =
            "local values = redis.call('hmget',KEYS[1],'recvCnt','couponCnt');\n" +  //lua返回value的数组
                    "if tonumber(values[1]) < tonumber(values[2]) then \n" +       //lua的数组索引从1开始 values[1] = recvCnt,values[2] = couponCnt
                    "  redis.call('hincrby',KEYS[1],'recvCnt',1);\n" +
                    "  return true;\n" +
                    "else\n " +
                    "  return false;\n" +
                    "end\n";

4.2 redis调用lua脚本

        //执行调用
        execute(GET_COUPON_CODE, keys);
        //此处将数值类型转化为Long
	    public Long execute(String redisScript,List<String> keys){
		      RedisScript<Long> REDIS_SCRIPT = new DefaultRedisScript<>(redisScript, Long.class);
		      return redisTemplate.execute(REDIS_SCRIPT,keys);
	}

4.3 lua基本用法

  redis.call()
  redis.pcall()
  call与pcall基本上一样。脚本报错时,call会直接报错,pcall不会报错,会把错误信息放到lua table 的err字段中。

5. 说明

  1. 记得利用“\n”分行,也可以利用string的append拼接
  2. values[n]对应有序数组keys,需要控制好各数据顺序
  3. lua的数组索引从1开始

6. 总结

我们先分析场景,通过多种方案对比,选用了redis+lua的组合来满足我们的业务需要。利用redis单线程的特点,以及redis2.6版本后开始对lua的支持,我们采用redis执行lua脚本来确保我们查询+修改的串行执行。后面我们展示了code的实现案例,以及介绍了lua脚本的一些注意事项,可以依葫芦画瓢形式自己实现自己的需求。综合而言,我们分析场景应先分析其核心问题,然后利用一些更简洁的方法或小技巧来落地。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://blog.wyatt.plus/?p=52

Buy me a cup of coffee ☕.