原理
1.初始化:
秒杀商品,将商品以list数据类型存入redis(每个数量为一个元素);
2.购买:
1)购买用户入队列,如果用户队列长度超过指定的排队长度,则返回排队数过多。;
2)如果用户队列长度小于指定的排队长度,然后生成订单,减去库存。下单完成
代码
MiaoshaController.php
<?php
namespace App\Http\Controllers;
use App\Models\Goods;
use App\Models\Orders;
use App\Services\RedisLock;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class MiaoshaController extends Controller
{
//排队人数
private $listNumber = 50;
/**
* 实现秒杀
*/
public function secondsKill(Request $request)
{
try {
$user_data = $request->only(['user_id', 'goods_id']);
if ( !$user_data ) {
return $this->failed('数据不能为空');
}
$goodsId = $user_data['goods_id'];
$userId = $user_data['user_id'];
#访问用户入队接口
$user_list = $this->requestUser($userId);
if ( !$user_list ) {
return $this->failed('排队数大于商品总数:'.Redis::llen('user_list'));
}
#消费商品,从队列中取出商品
$count = Redis::lpop('goods_store:' . $goodsId);
if(!$count) {
return $this->failed('商品抢光了');
}
#进入redis锁
$redisLock = new RedisLock($userId);
$lock = $redisLock->lock();
if ($lock) {
#最后进入数据库操作(每次固定消费1个)
$mysql_data = $this->storeOrder($userId, $count, '1');
if ( !$mysql_data ) {
$redisLock->unlock();
return $this->failed('生成订单失败');
} else {
#关闭锁
$redisLock->unlock();
return $this->success('抢购成功');
}
}
return $this->success('生成订单失败');
} catch (Exception $e) {
throw $e;
}
}
/**
* 将商品加入redis
*/
public function storageGoods(Request $request)
{
try {
#查询商品
$resutl = Goods::where('id', $request->goods_id)->select(['store','id'])->first();
$store = $resutl->store;
$res = Redis::llen('goods_store:' . $resutl->id);
$count = $store - $res;
for($i = 0;$i < $count; $i++){
Redis::lpush('goods_store:' . $resutl->id, $resutl->id);
}
return $this->success('加入成功'.$count);
} catch (Exception $e) {
throw $e;
}
}
/**
* 将用户也存入队列中(就是将访问请求数据)(此处没有进行用户过滤,同一个用户进行多次请求也会进入队列)
*/
private function requestUser($userId)
{
$res = Redis::llen('user_list');
#判断排队数
if ($res = Redis::llen('user_list') > $this->listNumber) {
// return '排队数大于商品总数';
return false;
}
#添加数据
Redis::lpush('user_list', $userId);
return true;
}
/*
*下单
*/
private function storeOrder($user_id, $goods_id, $number)
{
try {
#开启事务
DB::beginTransaction();
#查询库存sharedLock()共享锁,可以读取到数据,事务未提交不能修改,直到事务提交
#lockForUpdate()不能读取到数据
$resutl = Goods::where(['id'=>$goods_id])->lockForUpdate()->first();
#添加订单
if ( $resutl ) {
$resutl_order = Orders::create([
'user_id' => $user_id,
'goods_id' => $goods_id,
'goods_number' => $number,
'ordersn' => $this->buildOrderNo(),
'price' => $resutl->price,
]);
#减少库存
$resutl_update = Goods::where('id',$goods_id)->where('store', '>', 0)->decrement('store');
#将用户从队列里面弹出,允许下一个用户进来
Redis::rpop('user_list');
if ($resutl_order->id > 0 && $resutl_update > 0) {
DB::commit();
return true;
}
}
DB::rollBack();
return false;
} catch(Exception $e) {
throw $e;
}
}
/**
* 生成唯一订单号
*/
private function buildOrderNo(){
return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
}
RedisLock.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
class RedisLock
{
private $id;
public function __construct($id)
{
$this->id= $id;
}
public function lock() {
return Redis::set("orders:lock", $this->id, "nx", "ex", 10);
}
function unlock() {
$script = redis.call("get",KEYS[1]) == ARGV[1];
if ($script){
return redis.call("del",KEYS[1])
}else{
return 0
}
return Redis::eval($script, 1, "orders:lock", intval($this->id));
}
}