Redis加锁命令分有INCR、SETNX、SET
一、INCR锁
key不存在时,key的值会先被初始化为0,其它用户在执行INCR操作进行加一,
如果返回的数大于1,说明这个锁正在被使用当中,通常用在同时只能有一个人可以操作某个行为。
$redis->incr($key);
$redis->expire($key, $time); //过期时间
$redis->del($key); //删除锁
1、 客户端A请求服务器获取key的值为1表示获取了锁
2、 客户端B也去请求服务器获取key的值为2表示获取锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求的时候获取key的值为1表示获取锁成功
5、 客户端B执行代码完成,删除锁
二、SETNX锁
当key不存在时,将key设置为value,如果key已存在,则SETNX不做任何动作。
$redis->setNX($key, $value);
$redis->expire($key, $time); //过期时间
$redis->del($key); //删除锁
1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁
三、SET锁
$redis->set($key, $value, array('nx', 'ex' => $time)); //ex表示秒
$redis->del($key); //删除锁
1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁
PHP实现setnx锁
1.创建以下两张数据表
CREATE TABLE `storage` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`number` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
CREATE TABLE `order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`number` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
2.测试不加锁情况
$pdo = new PDO('mysql:host=127.0.0.1;dbname=demo', 'demo', 'demo');
$sql="select `number` from storage where id=1 limit 1";
$res = $pdo->query($sql)->fetch();
$number = $res['number'];
if($number>0){
$sql ="insert into `order` VALUES (null,$number)";
$order_id = $pdo->query($sql);
if($order_id){
$sql="update storage set `number`=`number`-1 WHERE id=1";
$pdo->query($sql);
}
}
window系统在cmd命令,进入apache\bin目录执行 ab -n 800 -c 800 域名/ 模拟800次并发请求
通过以上结果看到,库存和订单都超出了
3.测试加锁情况
class Lock {
private static $instance ;
private $redis;
private function __construct() {
$this->redis = new Redis();
$this->redis ->connect('127.0.0.1');
}
public static function getInstance() {
if(self::$instance instanceof self) {
return self::$instance;
}
return self::$instance = new self();
}
/**
* @function 加锁
* @param $key 锁名称
* @param $expTime 过期时间
*/
public function set($key,$expTime){
//初步加锁
$isLock = $this->redis->setnx($key,time()+$expTime);
if($isLock){
return true;
}else{
//加锁失败的情况下。判断锁是否已经存在,如果锁存在切已经过期,那么删除锁。进行重新加锁
$val = $this->redis->get($key);
if($val && $val<time()){
$this->del($key);
}
return $this->redis->setnx($key,time()+$expTime);
}
}
/**
* @param $key 解锁
*/
public function del($key){
$this->redis->del($key);
}
}
$pdo = new PDO('mysql:host=127.0.0.1;dbname=demo', 'demo', 'demo');
$lockObj = Lock::getInstance();
//判断是能加锁成功
if($lock = $lockObj->set('storage',10)){
$sql="select `number` from storage where id=1 limit 1";
$res = $pdo->query($sql)->fetch();
$number = $res['number'];
if($number>0){
$sql ="insert into `order` VALUES (null,$number)";
$order_id = $pdo->query($sql);
if($order_id){
$sql="update storage set `number`=`number`-1 WHERE id=1";
$pdo->query($sql);
}
}
//解锁
$lockObj->del('storage');
}else{ //加锁不成功执行其他操作。
}
执行以上相同的并发请求,结果如下