php+redis+lua脚本的库存加减原子操作

	/***
	 *	减库存
     * @return void
     */
    public function stockLuaDecr($goodsList = [])
    {
    	//class上面自行引用一下 use app\common\library\token\driver\Redis; 
    	$redis = new Redis();
    	#先将用户提交订单的商品列表处理成以下格式
        $goodsList = [
            [
                'nums' => 2,//购买的数量
                'redisKey' => 'goods_sku:1'//Redis的key
            ],
            [
                'nums' => 2,//购买的数量
                'redisKey' => 'goods_sku:2'//Redis的key
            ],
            [
                'nums' => 1,//购买的数量
                'redisKey' => 'goods_sku:3'//Redis的key
            ]
        ];
        //这里的<<<是必须的,后面lua大小写都可以(LUA),包括用script(SCRIPT)也一样能执行,
        //只需保证头尾标签一致就行
        $lua = <<<lua
        --KEYS[1]就是eval方法的第二参数传过来的数组,用json_encode做了转换
        --lua中数组的下标是从1开始,跟php有区别,KEYS[0]是错误的,都是从KEYS[1]开始,
        --相当于eval第二个参数中的数组下标的0
        --可以把local理解为js里面的var,就是声明局部变量用的
        local data = KEYS[1]
        --将json格式转化成数组,同理还可以将数组转化成json用于返回(cjson.encode(数组))
        local data = cjson.decode(data)
        local success = 1
        --k和v就相当于是foreach的key和value,循环的时候可以直接用v["字段名"]
        --in pairs(数组) do 是固定写法,结尾用end,if (条件) then 代码 end 也是固定写法
        --校验key是否存在
        for k, v in pairs(data) do
        	--如果key不存在,直接返回false,如果想返回具体内容,可以对return返回值进行修改
        	--,这里不再举例
            if (redis.call("exists",v["redisKey"]) == 0) 
            then
                return false
            end
        end
        --减全部购买的库存
        for k, v in pairs(data) do
        	--如果decrby之后的库存变成负数了,就把定义的'success'这个变量改成0,
        	--暂时不用立刻加回来,反正只要有1个失败了就都要回滚
            if (redis.call("decrby", v["redisKey"], v["nums"]) < 0)
            then
                success = 0     
            end
        end
        --如果存在库存小于0的商品,再将全部商品库存加回来,相当于回滚操作
        if (success == 0) 
        then
            for k, v in pairs(data) do
                redis.call("incrby", v["redisKey"], v["nums"])
            end
            return false
        end
        return true
lua;
		//eval方法第一个参数是脚本,第二个参数是数组,里面放一个json_decode的数组就可了,
		//第三个参数是第二个参数数组元素的个数,本例中第二个参数数组只有一个元素,所以
		//第三个参数就是1,需要变通的写法可自行百度
        if (!$redis->handler()->eval($lua, [json_encode($goodsList, JSON_UNESCAPED_UNICODE)], 1)) {
            return false;
        }
        return true;
  	}

	/**
     * 回滚加库存
     * @param $goodsList
     * @return void
     */
    public function stockLuaIncr($goodsList = [])
    {
        $goodsList = [
            [
                'nums' => 2,
                'redisKey' => 'goods_sku:1'
            ],
            [
                'nums' => 2,
                'redisKey' => 'goods_sku:2'
            ],
            [
                'nums' => 1,
                'redisKey' => 'goods_sku:3'
            ]
        ];
        $lua = <<<lua
        local data = KEYS[1]
        local data = cjson.decode(data)
        for k, v in pairs(data) do
            redis.call("incrby", v["redisKey"], v["nums"])
        end
lua;
        $redis->handler()->eval($lua, [json_encode($goodsList, JSON_UNESCAPED_UNICODE)], 1);
    }

以上方法适用于创建订单时的库存校验,应放在db业务处理层的外面

	public function createOrder()
    {
        $goodsList = input('goods_list');
        //执行lua库存校验
        if (!$this->stockLuaDecr($goodsList)) {
            $this->error('库存不足');
        }
        try {
            //DB层业务操作逻辑
        } catch (\Exception $m) {
            //如果DB层出问题了,需要把上面减掉的redis库存再加回来
            $this->stockLuaIncr($goodsList);
            $this->error('下单失败');
        }
        $this->success('下单成功');
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值