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 Rename 更改文件、文件夹名称
May 24 PHP
PHP中设置时区方法小结
Jun 03 PHP
回帖脱衣服的图片实现代码
Feb 15 PHP
php的array数组和使用实例简明教程(容易理解)
Mar 20 PHP
ThinkPHP中I(),U(),$this-&gt;post()等函数用法
Nov 22 PHP
php中 $$str 中 &quot;$$&quot; 的详解
Jul 06 PHP
配置eAccelerator和XCache扩展来加速PHP程序的执行
Dec 22 PHP
PHP生成短网址方法汇总
Jul 12 PHP
php 防止表单重复提交两种实现方法
Nov 03 PHP
PHP读取zip文件的方法示例
Nov 17 PHP
Laravel中如何增加自定义全局函数详解
May 09 PHP
PHP设计模式之建造者模式(Builder)原理与用法案例详解
Dec 12 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实现斐波那契数列的简单写法
2014/07/19 PHP
常用PHP框架功能对照表
2014/10/23 PHP
PHP实现通过二维数组键值获取一维键名操作示例
2019/10/11 PHP
javascript+css 网页每次加载不同样式的实现方法
2009/12/27 Javascript
JavaScript 在网页上单击鼠标的地方显示层及关闭层
2012/12/30 Javascript
瀑布流布局代码一例
2014/04/11 Javascript
一个JavaScript函数把URL参数解析成Json对象
2014/09/24 Javascript
jquery幻灯片插件bxslider样式改进实例
2014/10/15 Javascript
JS实现的通用表单验证插件完整实例
2015/08/20 Javascript
jquery中实现时间戳与日期相互转换
2016/04/12 Javascript
JQuery核心函数是什么及使用方法介绍
2016/05/03 Javascript
当jquery ajax遇上401请求的解决方法
2016/05/19 Javascript
onmouseover事件和onmouseout事件全面理解
2016/08/15 Javascript
基于SpringMVC+Bootstrap+DataTables实现表格服务端分页、模糊查询
2016/10/30 Javascript
jquery网页加载进度条的实现
2017/06/01 jQuery
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
2017/09/18 Javascript
微信小程序左滑动显示菜单功能的实现
2018/06/14 Javascript
ES6知识点整理之数组解构和字符串解构的应用示例
2019/04/17 Javascript
Node.js API详解之 util模块用法实例分析
2020/05/09 Javascript
phpsir 开发 一个检测百度关键字网站排名的python 程序
2009/09/17 Python
python静态方法实例
2015/01/14 Python
在Pycharm中对代码进行注释和缩进的方法详解
2019/01/20 Python
pyqt5 获取显示器的分辨率的方法
2019/06/18 Python
python实现高斯(Gauss)迭代法的例子
2019/11/20 Python
使用TensorFlow-Slim进行图像分类的实现
2019/12/31 Python
pycharm无法安装第三方库的问题及解决方法以scrapy为例(图解)
2020/05/09 Python
Python爬虫实例——爬取美团美食数据
2020/07/15 Python
使用sublime text3搭建Python编辑环境的实现
2021/01/12 Python
Vans奥地利官方网站:美国原创极限运动潮牌
2018/09/30 全球购物
美国厨房和园艺工具网上商店:Nestneed
2019/08/24 全球购物
可以在一个PHP文件里面include另外一个PHP文件两次吗
2015/05/22 面试题
五年级英语教学反思
2014/01/31 职场文书
四风问题自查报告剖析材料
2014/02/08 职场文书
大专毕业生求职信
2014/07/05 职场文书
如何写好开幕词?
2019/06/24 职场文书
Python scrapy爬取起点中文网小说榜单
2021/06/13 Python