当前位置: 首页 > 后端技术 > PHP

php和redis设计秒杀活动

时间:2023-03-29 23:51:10 PHP

1说明前段时间面试的时候总是被问到如何设计一个秒杀活动,但是我没有这方面的实践经验,只好根据自己的理解和一些资料设计了这样一个程序,主要是利用redis的string和set,string主要是利用它的k-v结构来处理库存,list的数据结构也可以用来处理商品库存,set用来保证用户重复提交。其中,我们主要解决的问题是防止并发Oversold/Oversold2流程设计3代码3.1服务器代码类MiaoSha{constMSG_REPEAT_USER='不要重复参与';constMSG_EMPTY_STOCK='库存不足';constMSG_KEY_NOT_EXIST='密钥不存在';constIP_POOL='ip_pool';constUSER_POOL='user_pool';/**@varRedis*/public$redis;公共$密钥;公共函数__construct($key=''){$this->checkKey($key);$this->redis=newRedis();//todo连接池$this->redis->connect('127.0.0.1');}publicfunctioncheckKey($key=''){if(!$key){thrownewException(self::MSG_KEY_NOT_EXIST);}else{$this->key=$key;}}publicfunctionsetStock($value=0){if($this->redis->exists($this->key)==0){$this->redis->set($this->键,$值);}}publicfunctioncheckIp($ip=0){$sKey=$this->key.自::IP_POOL;如果(!$ip||$this->redis->sIsMember($sKey,$ip)){thrownewException(self::MSG_REPEAT_USER);}}publicfunctioncheckUser($user=0){$sKey=$this->key.自我::USER_POOL;如果(!$user||$this->redis->sIsMember($sKey,$user)){thrownewException(self::MSG_REPEAT_USER);}}publicfunctioncheckStock($user=0,$ip=0){$num=$this->redis->decr($this->key);如果($num<0){抛出新异常(self::MSG_EMPTY_STOCK);}else{$this->redis->sAdd($this->key.self::USER_POOL,$user);$this->redis->sAdd($this->key.self::IP_POOL,$ip);//todo添加到mysqlecho'success'.PHP_EOL;error_log('成功'.$user.PHP_EOL,3,'/var/www/html/demo/log/debug.log');}/***@note:这种做法不能阻止并发*@funccheckStockFail*@paramint$user*@paramint$ip*@throwsException*/publicfunctioncheckStockFail($user=0,$ip=0){$num=$this->redis->get($this->key);if($num>0){$this->redis->sAdd($this->key.self::USER_POOL,$user);$this->redis->sAdd($this->key.self::IP_POOL,$ip);//todo添加到mysqlecho'success'.PHP_EOL;error_log('成功'.$user.PHP_EOL,3,'/var/www/html/demo/log/debug.log');$num--;$this->redis->set($this->key,$num);}else{thrownewException(self::MSG_EMPTY_STOCK);}}}3.2客户端测试代码functiontest(){try{$key='cup_';$handler=new秒杀($key);$handler->setStock(10);$user=rand(1,10000);$ip=$用户;$handler->checkIp($ip);$handler->checkUser($user);$handler->checkStock($user,$ip);}catch(\Exception$e){echo$e->getMessage().PHP_EOL;error_log('失败'.$e->getMessage().PHP_EOL,3,'/var/www/html/demo/log/debug.log');}}functiontest2(){try{$key='cup_';$handler=new秒杀($key);$handler->setStock(10);$user=rand(1,10000);$ip=$用户;$handler->checkIp($ip);$handler->checkUser($user);$handler->checkStockFail($user,$ip);//不能防止并发}catch(\Exception$e){echo$e->getMessage().PHP_EOL;error_log('失败'.$e->getMessage().PHP_EOL,3,'/var/www/html/demo/log/debug.log');}}4测试测试环境说明ubantu16.04redis2.8.4php5.5在服务端代码中,我们有两个函数,checkStock和checkStockFail,其中checkStockFail不能在高并发情况下表现不佳,保证存货时不能终止运行在redis级别为0。我们使用ab工具来测试wherewww.hello.com是配置的虚拟主机名flash-sale.php是我们脚本的名字#1stcase500并发使用客户端的test2()执行ab-n500-c100www.hello.com/flash-sale.php日志记录结果:#第二种情况5000并发使用客户端的test2()执行ab-n5000-c1000www.hello.com/flash-sale.php日志记录结果:#第三种情况,使用客户端的test()500并发下执行ab-n500-c100www.hello.com/flash-sale.phplogrecordresults:#第4种情况,使用client的test()执行ab-n5000-c1000www.hello.com/flash-sale.phplog5000并发下的日志记录结果:5总结我们从日志中可以清楚的看到,在第3和第4种情况下,商品的数量始终可以保证是我们设置为10的库存值,但是在第1和第2种情况下,出现了超额卖redis到的现象controlconcurrency主要是利用了其api的原子操作。从checkStock和checkStockFail可以看出,一种是直接把库存减1个操作,所以没有并发,但是另一种方法是先把库存值取出来,减一,再赋值。这样并发下,多个进程会读取库存中多个1的值,所以会出现超卖的情况。