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