网上的签到大部分都很复杂表示有的看不懂,直接用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);