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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章