php + redis 实现秒杀
一、概述
秒杀这个问题在很多面试的时候都会提到,会有各个方面的调优,配置等等,本文在这里举个简单的例子来演示下秒杀的过程,供大家参考发散思维。
二、准备介绍
- thinkPHP6框架
- redis6.0
- php7.4
- redis操作包predis
- apache-jmeter-5.6
三、代码实现
秒杀controller层的简易代码:
<?php /** * Created by PhpStorm * Author: fengzi * Date: 2024/4/8 * Time: 10:07 */ namespace app\admin\controller\seckill; use app\admin\service\seckill\SeckillService; class SeckillController { public function __construct() { } /** * 设置商品数量 * @param SeckillService $seckillService * @return \think\response\Json * @Author: fengzi * @Date: 2024/4/8 10:00 */ public function setNumber(SeckillService $seckillService) { $seckillService->setProductNumber(); try { $seckillService->setProductNumber(); return success('设置成功'); } catch (\Exception $e) { return error($e->getMessage()); } } /** * 购买入口 * @param SeckillService $seckillService * @return \think\response\Json * @Author: fengzi * @Date: 2024/4/8 11:00 */ public function index(SeckillService $seckillService) { try { $seckillService->seckill(); return success(); } catch (\Exception $e) { return error($e->getMessage()); } } }
秒杀service层的简易代码:
<?php /** * Created by PhpStorm * Author: fengzi * Date: 2024/4/8 * Time: 10:09 */ namespace app\admin\service\seckill; use Predis\Client; class SeckillService { private Client $redisService; public function __construct() { // 初始化Redis连接 $this->redisService = new Client(config('cache.stores.redis')); } /** * 设置秒杀的产品数量 * @return void * @Author: fengzi * @Date: 2024/4/8 10:11 */ public function setProductNumber() { $this->redisService->set('product_number', 20); } /** * 实现秒杀过程 * 一个用户只能购买一次商品 * @return void * @Author: fengzi * @Date: 2024/4/8 16:45 */ public function seckill() { //lua脚本,保证原子性 $lua_script = <<<LUA local number = redis.call('get', KEYS[1]) if (redis.call('exists', KEYS[1]) == 1 and tonumber(number) > 0 ) then redis.call('decr', KEYS[1]) redis.call('sadd', KEYS[2], ARGV[1]) return true else return false end LUA; //模拟用户ID $userId = mt_rand(1, 50); //抢购成功的用户列表 $userLists = 'user_lists_id'; try { $res = $this->redisService->sismember($userLists, $userId); if ( $res == 0 ) { $result = $this->redisService->eval($lua_script, 2, 'product_number', 'user_lists_id', $userId); if ($result) { // 处理订单逻辑 echo "秒杀成功!!!!"; } else { echo "秒杀结束!"; } } else { echo "秒杀失败,用户{$userId}已经秒杀过了!"; } } catch (\Exception $e) { echo "秒杀失败:" . $e->getMessage(); } } }
接口路由:
<?php use think\facade\Route; /**秒杀*/ Route::group('/seckill', function () { //设置秒杀数量 Route::any('set_number', '/seckill.Seckill/setNumber'); //购买 Route::any('index', '/seckill.Seckill/index'); });
四、测试
使用jmeter对接口进行压测,查看是否有超卖,一人购买多次的情况。
1、设置20个商品数量
2、配置 jmeter ,一秒请求200个,请求结果如下:
3、如下图所示,20个商品已经售卖完,未出现超卖。
4、如下图所示,购买人是20个,且无重复购买的人。
五、总结
实际生产环境的“秒杀”业务是很繁杂的,有许多地方需要注意。本文只是对“秒杀”这个场景做了简单的试验,仅给大家提供个思路和样本。如有不同见解欢迎大家留言讨论。
本文来自博客园,作者:疯子丶pony,转载请注明原文链接:https://www.cnblogs.com/mklblog/p/18121829