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语言中global和$GLOBALS[]的分析 之二
Feb 02 PHP
PHP中数组的三种排序方法分享
May 07 PHP
smarty内置函数capture用法分析
Jan 22 PHP
php生成图片验证码
Jun 09 PHP
PHP函数超时处理方法
Feb 14 PHP
PHP微信开发用Cache 解决数据缓存
Jul 11 PHP
php提交表单时保留多个空格及换行的文本样式的方法
Jun 20 PHP
php unlink()函数使用教程
Jul 12 PHP
thinkphp5框架调用其它控制器方法 实现自定义跳转界面功能示例
Jul 03 PHP
关于Anemometer图形化显示MySQL慢日志的工具搭建及使用的详细介绍
Jul 13 PHP
2020最新版 PhpStudy V8.1版本下载安装使用详解
Oct 30 PHP
PHP的imageTtfText()函数深入详解
Mar 03 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
ThinkPHP模板IF标签用法详解
2014/07/01 PHP
PHP生成json和xml类型接口数据格式
2015/05/17 PHP
用roll.js实现的图片自动滚动+鼠标触动的特效
2007/03/18 Javascript
javascript web对话框与弹出窗口
2009/02/22 Javascript
js下用eval生成JSON对象
2010/09/17 Javascript
JS判断不同分辨率调用不同的CSS样式文件实现思路及测试代码
2013/01/23 Javascript
原生js仿jquery一些常用方法(必看篇)
2016/09/20 Javascript
JS开发中百度地图+城市联动实现实时触发查询地址功能
2017/04/13 Javascript
IntersectionObserver实现图片懒加载的示例
2017/09/29 Javascript
nginx+vue.js实现前后端分离的示例代码
2018/02/12 Javascript
JavaScript基于数组实现的栈与队列操作示例
2018/12/22 Javascript
JavaScript 实现自己的安卓手机自动化工具脚本(推荐)
2020/05/13 Javascript
Nuxt的路由动画效果案例
2020/11/06 Javascript
[01:00:14]DOTA2-DPC中国联赛 正赛 Ehome vs Elephant BO3 第二场 2月28日
2021/03/11 DOTA
python基础教程之udp端口扫描
2014/02/10 Python
Python+selenium实现截图图片并保存截取的图片
2018/01/05 Python
浅析Python3中的对象垃圾收集机制
2019/06/06 Python
Ubuntu18.04中Python2.7与Python3.6环境切换
2019/06/14 Python
解决django的template中如果无法引用MEDIA_URL问题
2020/04/07 Python
python 代码运行时间获取方式详解
2020/09/18 Python
Python爬虫回测股票的实例讲解
2021/01/22 Python
欧洲第一的摇滚和金属乐队服装网站:EMP
2017/10/26 全球购物
新西兰Bookabach:查找全球度假屋
2020/12/03 全球购物
英语专业毕业生求职简历的自我评价
2013/10/24 职场文书
护理工作感言
2014/01/16 职场文书
社区母亲节活动记录
2014/03/06 职场文书
公务员平时考核实施方案
2014/03/11 职场文书
充分就业社区汇报材料
2014/05/07 职场文书
小学学校评估方案
2014/06/08 职场文书
低碳日宣传活动总结
2014/07/09 职场文书
社区个人对照检查材料(群众路线)
2014/09/26 职场文书
小学教师学习党的群众路线教育实践活动心得体会
2014/10/31 职场文书
初一军训感言
2015/08/01 职场文书
小学主题班会教案
2015/08/17 职场文书
2016年师德先进个人事迹材料
2016/02/29 职场文书
MySQL into_Mysql中replace与replace into用法案例详解
2021/09/14 MySQL