PHP使用Redis悲观锁简单实现每日签到功能,防止并发数据重复

网上的签到大部分都很复杂表示有的看不懂,直接用Mysql也是可以做,但是每次查询很消耗内存,还有很多的并发问题,所以想到利用Redis的缓存时间来做

提到悲观锁,先通过网上给出的一个比较形象的比喻

拿健身房比喻,门口挂着把钥匙(只有一把),想进去的人必须拿到这把钥匙才行,拿到钥匙的人可以进入,不管是热身、喝水还是跑步都可以,直到他出来把钥匙挂回墙上,下一个才能去争取,拿到的才可以再进去

听着好像有点不人性化,所以悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。乐观锁则适用于读多写少,并发冲突少的场景。

实现要点和思路

1、一个任务在同一时间段内只能被一个用户所持有;
2、避免出现死任务,即避免任务被用户长时间占有,无法释放。

设置一个锁的key,setnx是原子操作,只能一个进程写入成功,写入成功返回true(表示获取锁权限),然后写入内容立即释放锁即删除锁key。如果只用SETNX命令设置锁的话,如果当持有锁的进程崩溃或删除锁失败时,其他进程将无法获取到锁,问题就大了。获取不到锁的进程去判断锁的剩余有效时间,如果为-1,那么表示没有设置过期时间,则设置锁的有效时间为5秒(预留5秒给拿到锁的进程处理时间,足够多了),返回true,等待锁删除。

//每日签到
public function sign_in(){
    $this->load->model('user_model');
    $this->load->model('account_log_model');
    $this->load->model('config_model');
    $user_id = $this->_getId();
    $change_desc="每日签到送积分";
    //获取今天结束的时间   23:59:59
    $endTime=mktime(23,59,59,date('m'),date('d'),date('y'));
    //获取现在签到的时间
    $starTima=time();
    //查询是否签到
    include APPPATH.'config/load_redis.php';
    //设置DB 2 为签到数据库
    $redis->select(2);
    $lock_key= "LOCK_PREFIX".$user_id;
    $redis_user_locak = $redis->setnx($lock_key,1);  //设置锁,只是锁定的某个会员,其他人不受影响
    if($redis_user_locak){  //获取锁权限成功
        $redis_data = $redis->get($user_id);
        //如果redis没数据,则进行签到
        if(empty($redis_data)){
            $config_info = $this->config_model->getConfigInfo(array('code' => 'login_points'));
            if ($config_info['value'] > 0) {
                $logResult=$this->account_log_model->log_account_change($user_id, 0, 0, 0, $config_info['value'], $change_desc,Account_log_model::ACT_SIGN_INTEGRAL);
            }
            if($logResult){
                //签到成功计入redis 并且计算过期时间   今天结束的时间减去现在时间
                $expiration_time = $endTime - $starTima;
                $redis->set($user_id,json_encode(array('user_id'=>$user_id,'add_time'=>$starTima)),$expiration_time);
                $redis->del($lock_key);   //释放锁
                //写入数据库
                $this->db->query("insert into ecs_user_sign_in (user_id,sign_time,pay_points) values('$user_id','".date("Y-m-d H:i:s",time())."','".$config_info['value']."')");
                $this->_tojson(200, '签到成功');
            }
        }else{
            if($redis->ttl($lock_key) == -1){  //防止死锁
                $redis->expire($lock_key,3);
            }
            $this->_tojson(400, '该用户已经签到');
        }
    }else{
        if($redis->ttl($lock_key) == -1){  //防止死锁
            $redis->expire($lock_key,3);
        }
        $this->_tojson(400, '请勿重复签到!');
    }

}

每天用户签到之后,Redis 存贮用户ID 为下标的字符串类型,过期时间设置为 每天结束的时间戳和现在签到的时间戳的差

Redis配置文件

<?php
		$redis = new Redis();
		//连接
		$redis->connect('127.0.0.1', 6379);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章