php和redis实现秒杀活动的流程


Posted in PHP onJuly 17, 2019

1 说明

前段时间面试的时候,一直被问到如何设计一个秒杀活动,但是无奈没有此方面的实际经验,所以只好凭着自己的理解和一些资料去设计这么一个程序

主要利用到了redis的string和set,string主要是利用它的k-v结构去对库存进行处理,也可以用list的数据结构来处理商品的库存,set则用来确保用户进行重复的提交

其中我们最主要解决的问题是

-防止并发产生超抢/超卖

2 流程设计

php和redis实现秒杀活动的流程

3 代码

3.1 服务端代码

class MiaoSha{

 const MSG_REPEAT_USER = '请勿重复参与';
 const MSG_EMPTY_STOCK = '库存不足';
 const MSG_KEY_NOT_EXIST = 'key不存在';

 const IP_POOL = 'ip_pool';
 const USER_POOL = 'user_pool';

 /** @var Redis */
 public $redis;
 public $key;

 public function __construct($key = '')
 {
  $this->checkKey($key);
  $this->redis = new Redis(); //todo 连接池
  $this->redis->connect('127.0.0.1');
 }

 public function checkKey($key = '')
 {
  if(!$key) {
   throw new Exception(self::MSG_KEY_NOT_EXIST);
  } else {
   $this->key = $key;
  }
 }

 public function setStock($value = 0)
 {
  if($this->redis->exists($this->key) == 0) {
   $this->redis->set($this->key,$value);
  }
 }

 public function checkIp($ip = 0)
 {
  $sKey = $this->key . self::IP_POOL;
  if(!$ip || $this->redis->sIsMember($sKey,$ip)) {
   throw new Exception(self::MSG_REPEAT_USER);
  }
 }

 public function checkUser($user = 0)
 {
  $sKey = $this->key . self::USER_POOL;
  if(!$user || $this->redis->sIsMember($sKey,$user)) {
   throw new Exception(self::MSG_REPEAT_USER);
  }
 }

 public function checkStock($user = 0, $ip = 0)
 {
  $num = $this->redis->decr($this->key);
  if($num < 0 ) {
   throw new Exception(self::MSG_EMPTY_STOCK);
  } else {
   $this->redis->sAdd($this->key . self::USER_POOL, $user);
   $this->redis->sAdd($this->key . self::IP_POOL, $ip);
   //todo add to mysql
   echo 'success' . PHP_EOL;
   error_log('success' . $user . PHP_EOL,3,'/var/www/html/demo/log/debug.log');
  }
 }

 /**
  * @note:此种做法不能防止并发
  * @func checkStockFail
  * @param int $user
  * @param int $ip
  * @throws Exception
  */
 public function checkStockFail($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 add to mysql
   echo 'success' . PHP_EOL;
   error_log('success' . $user . PHP_EOL,3,'/var/www/html/demo/log/debug.log');
   $num--;
   $this->redis->set($this->key,$num);
  } else {
   throw new Exception(self::MSG_EMPTY_STOCK);
  }
 }
}

3.2 客户端测试代码

function test()
{
 try{
  $key = 'cup_';
  $handler = new MiaoSha($key);
  $handler->setStock(10);
  $user = rand(1,10000);
  $ip = $user;
  $handler->checkIp($ip);
  $handler->checkUser($user);
  $handler->checkStock($user,$ip);
 } catch (\Exception $e) {
  echo $e->getMessage() . PHP_EOL;
  error_log('fail' . $e->getMessage() .PHP_EOL,3,'/var/www/html/demo/log/debug.log');
 }
}

function test2()
{
 try{
  $key = 'cup_';
  $handler = new MiaoSha($key);
  $handler->setStock(10);
  $user = rand(1,10000);
  $ip = $user;
  $handler->checkIp($ip);
  $handler->checkUser($user);
  $handler->checkStockFail($user,$ip); //不能防止并发的
 } catch (\Exception $e) {
  echo $e->getMessage() . PHP_EOL;
  error_log('fail' . $e->getMessage() .PHP_EOL,3,'/var/www/html/demo/log/debug.log');
 }
}

4 测试

测试环境说明

  • ubantu16.04
  • redis2.8.4
  • php5.5

在服务端代码里面我们有两个函数分别是checkStock和checkStockFail,其中checkStockFail不能在高并发的情况下效果很差,不能在redis层面保证库存为0的时候终止操作。

我们利用ab工具进行测试

其中 www.hello.com 是配置的虚拟主机名称 flash-sale.php 是我们脚本的名称

#第1种情况 500并发下 用客户端的test2()去执行
 ab -n 500 -c 100 www.hello.com/flash-sale.php

log日志的记录结果:

php和redis实现秒杀活动的流程

#第2种情况 5000并发下 用客户端的test2()去执行
 ab -n 5000 -c 1000 www.hello.com/flash-sale.php

log日志的记录结果:

php和redis实现秒杀活动的流程

#第3种情况 500并发下 用客户端的test()去执行
 ab -n 500 -c 100 www.hello.com/flash-sale.php

log日志的记录结果:

php和redis实现秒杀活动的流程

#第4种情况 5000并发下 用客户端的test()去执行
 ab -n 5000 -c 1000 www.hello.com/flash-sale.php

log日志的记录结果:

php和redis实现秒杀活动的流程

5 总结

我们从日志中可以很明显的看出第3、4中情况下,可以保证商品的数量总是我们设置的库存值10,但是在情况1、2下,则产生了超卖的现象

redis来控制并发主要是利用了其api都是原子性操作的优势,从checkStock和checkStockFail中可以看出,一个是直接decr对库存进行减一操作,所以不存在并发的情况,但是另一个方法是将库存值先取出做减一操作然后再重新赋值,这样的话,在并发下,多个进程会读取到多个库存为1的值,因此会产生超卖的情况

以上所述是小编给大家介绍的php和redis实现秒杀活动的流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

PHP 相关文章推荐
五个PHP程序员工具
May 26 PHP
PHP 裁剪图片成固定大小代码方法
Sep 09 PHP
Windows7下PHP开发环境安装配置图文方法
May 20 PHP
redis 队列操作的例子(php)
Apr 12 PHP
php笔记之:初探PHPcms模块开发介绍
Apr 26 PHP
Apache实现Web Server负载均衡详解(不考虑Session版)
Jul 05 PHP
php实现文件下载简单示例(代码实现文件下载)
Mar 10 PHP
CMS中PHP判断系统是否已经安装的方法示例
Jul 26 PHP
php版本的cron定时任务执行器使用实例
Aug 19 PHP
PHP利用MySQL保存session的实现思路及示例代码
Sep 09 PHP
PHP的全局错误处理详解
Apr 25 PHP
PHP查询大量数据内存耗尽问题的解决方法
Oct 28 PHP
php web环境和命令行环境下查找php.ini的位置
Jul 17 #PHP
php命名空间设计思想、用法与缺点分析
Jul 17 #PHP
php和C#的yield迭代器实现方法对比分析
Jul 17 #PHP
php基于协程实现异步的方法分析
Jul 17 #PHP
php学习笔记之字符串常见操作总结
Jul 16 #PHP
thinkPHP+mysql+ajax实现的仿百度一下即时搜索效果详解
Jul 15 #PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
Jul 12 #PHP
You might like
解析用PHP读写音频文件信息的详解(支持WMA和MP3)
2013/05/10 PHP
thinkPHP微信分享接口JSSDK用法实例
2017/07/07 PHP
javascript 选择文件夹对话框(web)
2009/07/07 Javascript
理解 JavaScript 预解析
2009/10/25 Javascript
Javascript图像处理思路及实现代码
2012/12/25 Javascript
javascript setTimeout和setInterval计时的区别详解
2013/06/21 Javascript
键盘上一张下一张兼容IE/google/firefox等浏览器
2014/01/28 Javascript
JavaScript定义类和对象的方法
2014/11/26 Javascript
jquery的幻灯片图片切换效果代码分享
2015/09/07 Javascript
JavaScript实现简单获取当前网页网址的方法
2015/11/09 Javascript
JS实现消息来时让网页标题闪动效果的方法
2016/04/20 Javascript
全面解析JavaScript里的循环方法之forEach,for-in,for-of
2020/04/20 Javascript
AngularJS中isolate scope的用法分析
2016/11/22 Javascript
微信小程序 封装http请求实例详解
2017/01/16 Javascript
Canvas + JavaScript 制作图片粒子效果
2017/02/08 Javascript
微信小程序自定义导航教程(兼容各种手机)
2018/12/12 Javascript
vue.js 子组件无法获取父组件store值的解决方式
2019/11/08 Javascript
Vue + Element-ui的下拉框el-select获取额外参数详解
2020/08/14 Javascript
python中如何使用正则表达式的集合字符示例
2017/10/09 Python
使用pandas读取文件的实现
2019/07/31 Python
浅谈Python type的使用
2019/11/19 Python
Pytorch 中retain_graph的用法详解
2020/01/07 Python
scrapy框架携带cookie访问淘宝购物车功能的实现代码
2020/07/07 Python
python实现三壶谜题的示例详解
2020/11/02 Python
HTML5实现分享到微信好友朋友圈QQ好友QQ空间微博二维码功能
2018/01/03 HTML / CSS
世界上最大的巴士旅游观光公司:Big Bus Tours
2016/10/20 全球购物
德国2018年度最佳在线药房:Bodfeld Apotheke
2019/11/04 全球购物
什么是触发器(trigger)? 触发器有什么作用?
2013/09/18 面试题
在网络中有两台主机A和B,并通过路由器和其他交换设备连接起来,已经确认物理连接正确无误,怎么来测试这两台机器是否连通?如果不通,怎么来判断故障点?怎么排
2014/01/13 面试题
高三上学期学习自我评价
2014/04/23 职场文书
党员干部四风问题整改措施思想汇报
2014/10/12 职场文书
教师党员自我评价2015
2015/03/04 职场文书
导游词之寿县报恩寺
2020/01/19 职场文书
手把手教你怎么用Python实现zip文件密码的破解
2021/05/27 Python
JavaScript利用html5新方法操作元素类名详解
2021/11/27 Javascript
豆瓣2021评分最高动画剧集-豆瓣评分最高的动画剧集2021
2022/03/18 日漫