redis2.6+
以下示例代码有部分封装,关注原理即可:
<?php
$resObj = new stdClass();
header("content-type:application/json");
if(!isset($_POST['id'])) exit("no");
$prod_id = intval($_POST['id']);
$proKey = "prod".$prod_id; // 拼凑成 prod101 这样的key,商品名称
$redis->zSetOperator->setName = "stock"; // 设置sorted set 的key名称
$myid = session_create_id(); // php7.1+
// Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。如果key不存在,则设置成功,返回1;如果key存在,则设置失败返回0
// 反夏去没置key=lock的値,好比就是争抢一个 "锁"
// 利用单个 Redis 命令的原子性,NX等同于setnx,EX代表过期时间,2秒后自动解锁
while(!$redis->client()->set("lock",$myid,["NX","EX"=>2]))
{
// 没抢到锁则一直循环
usleep(100000); // 休眠0.1秒,继续抢锁
}
// redis 没有在事务上增加任何维持原子性的机制,所以 redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不执行
// watch 的功能为监控一个或多个键,一旦其中有一个键被修改、删除、覆盖,接下来如果执行multi...EXEC就会失败。断开当前连接依然会监控,监控一直持续到EXEC命令执行就取消监听(所有被watch的key)
$redis->client()->watch("lock");
// 获取该商品的库存
$current_stock = $redis->zSetOperator->get($prodKey);
if($current_stock <= 0){
$resObj->msg = "no stock"; // 代表没有库存
$resObj->result = $current_stock;
$redis->client()->multi(Redis::MULTI) // 开启事务
->del("lock") // 删除键 lock,等同于释放锁,其他请求可以继续进入
->exec(); // 执行事务后自动取消了 watch 监听
}else{
if(isset($_GET['delay']) & intval($_GET['delay']) == 1) // 这里开始模拟卡顿,假设判断后,正好进来了许多并发、或者程序卡顿了
{
sleep(5); // 模拟卡顿5秒
}
}
$ret = $redis->client()->multi(Redis:MULTI)
->zIncrBy("stock",-1,$prodKey) // 库存减去1
->del("lock")
->exec();
if($ret){
$resObj->msg = "OK"; // 代表执行成功
$resObj->result = $ret[0]; // 返回减去1后的库存
}else{
$resObj->msg = "canceled"; // 代表没有执行
$resObj->result = $redis->zSetOperator->get($prodKey); // 返回目前库存
}
// 输出
exit(json_encode($resObj));