记一次php laravel redis 简易的秒杀活动
本次使用docker容器
1.秒杀的商品同步到redis数据库中
执行命令同步商品信息
docker exec -it laravel-app php artisan app:init-seckill-stock
<?php namespace App\Console\Commands; use App\Models\admin\Storage; use Illuminate\Console\Command; use Illuminate\Support\Facades\Redis; use App\Services\SeckillService; class InitSeckillStock extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'app:init-seckill-stock'; /** * The console command description. * * @var string */ protected $description = 'Command description'; /** * Execute the console command. */ public function handle(SeckillService $seckill) { $this->info("Initializing seckill stock into Redis..."); $products = Storage::where('is_usable', '>', 0)->get(); foreach ($products as $product) { $seckill->initStock($product->product_id, $product->sku_id, $product->qty, 360000); $this->info("Stock set for product {$product->product_id} - SKU {$product->sku_id}"); } $this->info("Seckill stock initialization complete."); } }

2.模拟秒杀活动
/** * * 执行秒杀活动 * * */ public function testKill(SeckillService $seckill){ $productID=36763; $sku_id=42590; for($i=0;$i<=1000;$i++){ $userID=rand(1,999); $result=$this->seckill($productID,$sku_id,$userID,$seckill); if ($result === -1) { echo "库存为 0,秒杀活动提前结束。\n"; break; // 立即打断循环 } } print_r('秒杀活动结束'); } public function seckill( $productId,$skuId,$userId,SeckillService $seckill) { $kc=$seckill->getStock($productId,$skuId); if($kc){ // 判断是否已秒杀(防止重复参与) if (Redis::sismember("seckill:user:".$productId.":".$skuId, $userId)) { Log::info("重复秒杀尝试", [ 'product_id' => $productId, 'sku_id' => $skuId, 'user_id' => $userId, 'time' => now()->toDateTimeString() ]); return false; } // 执行秒杀 if ($seckill->attemptSeckill($productId,$skuId)) { // 记录用户已秒杀 Redis::sadd("seckill:user:".$productId.":".$skuId, $userId); // 推入订单队列(简化示例) Redis::rpush("seckill:queue:".$productId.":".$skuId, json_encode([ 'product_id' => $productId, 'sku_id' => $skuId, 'user_id' => $userId, 'time' => time(), ])); return response()->json(['msg' => '秒杀成功,正在生成订单']); } else { return response()->json(['msg' => '秒杀失败,库存不足'], 410); } }else{ return -1; } }
3.进行redis 库存扣减
<?php namespace App\Services; use Illuminate\Support\Facades\Redis; class SeckillService { /** * 秒杀库存扣减 * * @param string $skuId SKU 编号 * @param string $skuId SKU 编号 * @return bool true 表示扣减成功,false 表示库存不足或出错 */ public function attemptSeckill(string $productId,string $skuId): bool { $key = "seckill:stock:".$productId.":".$skuId; // Lua 脚本:原子扣减库存 $lua = <<<LUA local stock = tonumber(redis.call("get", KEYS[1])) if not stock or stock <= 0 then return 0 end redis.call("decr", KEYS[1]) return 1 LUA; $result = Redis::eval($lua, 1, $key); return $result == 1; } /** * 初始化秒杀库存 * * @param string $productId SKU 编号 * @param string $skuId SKU 编号 * @param int $qty 库存数量 * @param int|null $ttl 过期时间(秒),可选 */ public function initStock(string $productId,string $skuId, int $qty, ?int $ttl = null): void { $key = "seckill:stock:".$productId.":".$skuId; Redis::set($key, $qty); if ($ttl) { Redis::expire($key, $ttl); } } /** * 获取当前库存 * * @param string $skuId * @return int|null */ public function getStock(string $productId,string $skuId): ?int { $key = "seckill:stock:".$productId.":".$skuId; $qty = Redis::get($key); return $qty !== null ? (int) $qty : null; } }

4.把订单信息推入到redis队列
手动开启执行命令
docker exec -it laravel-app php artisan seckill:consume
<?php namespace App\Console\Commands; use App\Jobs\ProcessSeckillOrder; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Redis; class ConsumeSeckillQueue extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'seckill:consume'; /** * The console command description. * * @var string */ protected $description = '从 Redis 队列中读取秒杀数据并派发队列 Job'; /** * Execute the console command. */ public function handle() { $queueKey = 'seckill:queue:36763:42590'; $processingKey = 'seckill:queue:processing'; Log::info("秒杀队列消费者启动,监听队列:$queueKey"); while (true) { $data = Redis::rpoplpush($queueKey,$processingKey); if ($data) { $payload = json_decode($data, true); try { if (is_array($payload)) { Log::info('派发秒杀任务', $payload); ProcessSeckillOrder::dispatch($payload['user_id'], $payload['product_id'],$payload['sku_id']); }else { Log::warning("Redis 中的数据无法解析:$data"); // 移除坏数据 Redis::lrem($processingKey, 0, $data); } }catch (\Exception $e) { Log::error("消费者异常中断:" . $e->getMessage()); sleep(1); } } else { usleep(50000); // 空队列,休眠50ms } } } }

5. 后台任务消费队列 Job
手动执行命令
docker exec -it laravel-app php artisan queue:work
<?php namespace App\Jobs; use App\Models\admin\SeckillOrders; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; //use Psy\Util\Str; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Str; class ProcessSeckillOrder implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $userId; protected $productId; protected $skuId; /** * Create a new job instance. */ public function __construct($userId, $productId,$skuId) { $this->userId = $userId; $this->productId = $productId; $this->skuId = $skuId; } /** * Execute the job. */ public function handle(): void { $processingKey = 'seckill:queue:processing'; $data = json_encode([ 'user_id' => $this->userId, 'product_id' => $this->productId, 'sku_id' => $this->skuId, ]); DB::beginTransaction(); try { // 防止重复写入 $exists = SeckillOrders::where('user_id', $this->userId) ->where('product_id', $this->productId) ->where('sku_id', $this->skuId) ->exists(); if (!$exists) { SeckillOrders::create([ 'user_id' => $this->userId, 'product_id' => $this->productId, 'sku_id' => $this->skuId, 'qty' => 1, 'order_no' => Str::uuid()->toString(), ]); // ✅ 数据写入成功,再从 processing 队列移除任务 Redis::lrem($processingKey, 0, $data); } Log::info('写入数据库成功'); DB::commit(); } catch (\Exception $e) { DB::rollBack(); Log::error("秒杀订单写入失败:".$e->getMessage()); } } }

浙公网安备 33010602011771号