秒杀一般会在短时间内对接口有大量的请求,并发量比较大,如果直接对数据库进行操作,会影响系统的稳定,而使用redis就可以尽量避免这个情况了。
下边用redis模拟了用户抢购的场景
<?php
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1');
// 模拟用户抢购,其中存在同一个用户多次点击
$userIds1 = range(1, 10);
$userIds2 = range(5, 15);
$userIds = array_merge($userIds1, $userIds2);
// 设置商品信息
$goodsId = 1;
$goodsStock = 5;
// 商品库存存储名称
$stockKey = 'goods_stock_' . $goodsId;
$redis->set($stockKey, $goodsStock);
// 设置锁避免并发
$lockKey = 'lock_' . $goodsId;
$time = time();
foreach ($userIds as $v){
// 设置商品每个用户只能抢一件
$userKey = 'user_' . $goodsId . '_' . $v;
// 判断用户是否已抢购
$user = $redis->get($userKey);
if ($user){
echo '用户【' . $v . '】该商品限购一件';
echo '<br/>';
continue;
}
$lock = $redis->setnx($lockKey, 1);
if ($lock){
// 设置过期时间
$redis->expire($lockKey, 2);
// 预减库存
$stock = $redis->decrBy($stockKey, 1);
if ($stock >= 0){
// 抢购成功则设置用户信息
$redis->set($userKey, $userKey);
$order_no = date("YmdHis") . rand(10000, 99999);
$data = [
'order_no' => $order_no,
'goods_id' => $goodsId,
'user_id' => $v,
'create_time' => $time
];
$redis->lPush('orders', json_encode($data));
echo '用户【' . $v . '】抢购成功,订单号【' . $order_no . '】';
}else{
$redis->incrBy($stockKey, 1);
echo '用户【' . $v . '】库存不足';
}
}else{
$redis->del($lockKey);
echo '用户【' . $v . '】系统繁忙';
}
echo '<br/>';
}
代码执行的结果如下
可以看到是不会出现超卖的情况,这里释放锁也可以放到抢购成功之后。当订单信息入列后可以在另一边执行出列,再把订单信息插入数据库。
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1');
// 移除并返回列表key的尾元素
$orderInfo = $redis->rPop('orders');
// 存在订单则加入数据库
if ($orderInfo){
// 模拟加入数据库
$fp = fopen('order.txt',"a");
fwrite($fp,date("Y-m-d H:i:s") . ':' . var_export($orderInfo,true)."\r\n");
fclose($fp);
}
echo $orderInfo;
以上操作就能实现一个简单的秒杀流程了,更具体的操作则可以根据项目需求不同而做出更改,
还可以使用队列方式实现秒杀。