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 高手之路(三)
Oct 09 PHP
php学习之 数组声明
Jun 09 PHP
php入门学习知识点一 PHP与MYSql连接与查询
Jul 14 PHP
PHP 代码规范小结
Mar 08 PHP
PHP5各个版本的新功能和新特性总结
Mar 16 PHP
CodeIgniter CLI模式简介
Jun 17 PHP
php中最简单的字符串匹配算法
Dec 16 PHP
PHP中开启gzip压缩的2种方法
Jan 31 PHP
PHP 自动加载的简单实现(推荐)
Aug 12 PHP
使用PHPExcel导出Excel表
Sep 08 PHP
laravel 字段格式化 modle 字段类型转换方法
Sep 30 PHP
laravel 修改记住我功能的cookie保存时间的方法
Oct 14 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
《五等分的花嫁》漫画完结!2020年10月第2期TV动画制作组换血!
2020/03/06 日漫
PHP中的错误处理、异常处理机制分析
2012/05/07 PHP
php调用google接口生成二维码示例
2014/04/28 PHP
PDO::getAvailableDrivers讲解
2019/01/28 PHP
jquery 插件实现图片延迟加载效果代码
2010/02/06 Javascript
jQuery中与toggleClass等价的程序段 以及未来学习的方向
2010/03/18 Javascript
jquery div 居中技巧应用介绍
2012/11/24 Javascript
用js读、写、删除Cookie代码续篇
2014/12/03 Javascript
jQuery简单tab切换效果实现方法
2015/04/08 Javascript
Bootstrap嵌入jqGrid,使你的table牛逼起来
2016/05/05 Javascript
微信小程序 animation API详解及实例代码
2016/10/08 Javascript
浅入深出Vue之自动化路由
2019/08/06 Javascript
ReactRouter的实现方法
2021/01/25 Javascript
Django中使用group_by的方法
2015/05/26 Python
python实现ID3决策树算法
2017/12/20 Python
基于python实现在excel中读取与生成随机数写入excel中
2018/01/04 Python
Python实现类似比特币的加密货币区块链的创建与交易实例
2018/03/20 Python
利用Python批量提取Win10锁屏壁纸实战教程
2018/03/27 Python
使用Python更换外网IP的方法
2018/07/09 Python
python生成1行四列全2矩阵的方法
2018/08/04 Python
Python基于SMTP协议实现发送邮件功能详解
2018/08/14 Python
对python判断是否回文数的实例详解
2019/02/08 Python
python中@property和property函数常见使用方法示例
2019/10/21 Python
Pandas时间序列:重采样及频率转换方式
2019/12/26 Python
django实现将后台model对象转换成json对象并传递给前端jquery
2020/03/16 Python
10个示例带你掌握python中的元组
2020/11/23 Python
html5如何在Canvas中实现自定义路径动画示例
2017/09/18 HTML / CSS
小学教师评语大全
2014/04/23 职场文书
中国梦我的梦演讲稿
2014/04/23 职场文书
自愿离婚协议书范文2014
2014/10/12 职场文书
挂靠协议书
2015/01/27 职场文书
街道社区活动报告
2015/02/05 职场文书
社区节水倡议书
2015/04/29 职场文书
2015年工程师工作总结
2015/04/30 职场文书
python中的被动信息搜集
2021/04/29 Python
教你使用Python获取QQ音乐某个歌手的歌单
2022/04/03 Python