PHP用redis实现请求加锁

11 篇文章 0 订阅
10 篇文章 0 订阅

场景:用户任务过程中产生的余额,在给用户增加余额的时候,用户表余额没有问题,详情中出现当前余额字段出现相同的情况;数据库的事务隔离级别是read Committed;QPS达到200时会出现这种情况;

分析:多个请求集中处理,开启事务A:查询余额此时余额100,开启事物B:查询余额 脚本更新user用户表balacce(余额)字段,然后提交事务A,此时user表balance字段已经更新为101 B事务还没有提交B事务余额变动还是在100的基础上;所以detail记录的余额就会出现两个100;

如下图所示: 

最终确认方案:给接口加进程锁 有两种实现方式;

1、php 文件进程锁; 

2、redis 的锁机制;

最终选择了利用redis的锁机制去实现;主要是setnx

代码如下:
接口控制器进行请求限制

$redis           = RedisHelper::getInstance(C('REDIS_HOST'), C('REDIS_PORT'), 0);
$redisLockModel      = M('redisLock');
$intUniqueLockId = false;
// 监测是否成功开启redis线程锁,开启成功继续往下执行 失败继续监测;
while(!$intUniqueLockId) {
    $intUniqueLockId = $redisLockModel->addRedisLock($redis,$data['user_id']);
    usleep(100);
}

 if ($intUniqueLockId) {
    // 进行返奖,这里省略
    $this->addFanjiang($fanjiangInfo, 1);
    // 释放线程锁
    $redisLockModel->releaseLock($redis,$data['user_id'], $intUniqueLockId);
}

封装的一些方法:

<?php

class redisLockModel
{
    const  REDIS_LOCK_DEFAULT_EXPIRE_TIME = 60;
    const  REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';
    const  REDIS_LOCK_KEY_TEMPLATE  = 'adddy_balance_lock_%s';

    /**
     * 生成锁唯一ID(通过Redis incr指令实现简易版本,可结合日期、时间戳、取余、字符串填充、随机数等函数,生成指定位数唯一ID)
     * @return mixed
     */
    public static function generateUniqueLockId($redis)
    {
        return $redis->incr(self::REDIS_LOCK_UNIQUE_ID_KEY);
    }

    /*
     * 加逻辑占用锁
     */
    public function addRedisLock($redis,$intAddBalanceId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
    {
        //生成唯一锁ID,解锁需持有此ID
        $intUniqueLockId = self::generateUniqueLockId($redis);

        // 根据前缀,结合用户ID,生成唯一Redis key 以用户为唯一单位
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intAddBalanceId);

        //加锁(通过Redis setnx指令实现,从Redis 2.6.12开始,通过set指令可选参数也可以实现setnx,同时可原子化地设置超时时间)
        $bolRes = $redis->set($strKey, $intUniqueLockId, ['nx', 'ex' => $intExpireTime]);

        // 加锁成功返回锁ID,加锁失败返回false
        return $bolRes ? $intUniqueLockId : $bolRes;
    }

    /*
     * 解逻辑占用锁
     */
    public function releaseLock($redis,$intAddBalanceId, $intUniqueLockId)
    {
        // 生成Redis key
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intAddBalanceId);
        // 监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控
        $redis->watch($strKey);
        if ($intUniqueLockId == $redis->get($strKey)) {
            $redis->multi()->del($strKey)->exec();
            $return = true;
        }
        $redis->unwatch();
        return isset($return) && $return ? true:false;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值