網上的簽到大部分都很複雜表示有的看不懂,直接用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);