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环境配置 php5 mysql5 apache2 phpmyadmin安装与配置
Nov 17 PHP
PHP 开源框架22个简单简介
Aug 24 PHP
php 空格,换行,跳格使用说明
Dec 18 PHP
非常精妙的PHP递归调用与静态变量使用
Dec 16 PHP
php中Ctype函数用法详解
Dec 09 PHP
php设计模式之单例模式实例分析
Feb 25 PHP
PHP数组与对象之间使用递归实现转换的方法
Jun 24 PHP
php中分页及SqlHelper类用法实例
Jan 12 PHP
ThinkPHP实现静态缓存和动态缓存示例代码
May 02 PHP
如何利用预加载优化Laravel Model查询详解
Aug 11 PHP
PHP实现的各类hash算法长度及性能测试实例
Aug 27 PHP
Ajax+PHP实现的删除数据功能示例
Feb 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
simplehtmldom Doc api帮助文档
2012/03/26 PHP
PHP语法自动检查的Vim插件
2014/08/11 PHP
php中header跳转使用include包含解决参数丢失问题
2015/05/08 PHP
phpMyAdmin安装并配置允许空密码登录
2015/07/04 PHP
WordPress中注册菜单与调用菜单的方法详解
2015/12/18 PHP
php装饰者模式简单应用案例分析
2019/10/23 PHP
PHP Swoole异步读取、写入文件操作示例
2019/10/24 PHP
javascript 用记忆函数快速计算递归函数
2010/03/15 Javascript
在UpdatePanel内jquery easyui效果失效的解决方法
2010/04/11 Javascript
用javascript作一个通用向导说明
2011/08/30 Javascript
JavaScript中“+”的陷阱深刻理解
2012/12/04 Javascript
一些常用弹出窗口/拖放/异步文件上传等实用代码
2013/01/06 Javascript
JS逆序遍历实现代码
2014/12/02 Javascript
jQuery弹出层插件Lightbox_me使用指南
2015/04/21 Javascript
jQuery实现首页图片淡入淡出效果的方法
2015/06/10 Javascript
jquery实现简单的自动播放幻灯片效果
2015/06/13 Javascript
详细解读JavaScript的跨浏览器事件处理
2015/08/12 Javascript
js不间断滚动的简单实现
2016/06/03 Javascript
jQuery实现指定区域外单击关闭指定层的方法【经典】
2016/06/22 Javascript
Angularjs单选改为多选的开发过程及问题解析
2017/02/17 Javascript
jQuery EasyUI Panel面板组件使用详解
2017/02/28 Javascript
关于jQuery库冲突的完美解决办法
2017/05/20 jQuery
基于Vue实现图书管理功能
2017/10/17 Javascript
Vue微信项目按需授权登录策略实践思路详解
2018/05/07 Javascript
使用Easyui实现查询条件的后端传递并自动刷新表格的两种方法
2019/09/09 Javascript
node.js域名解析实现方法详解
2019/11/05 Javascript
如何在vue中使用kindeditor富文本编辑器
2020/12/19 Vue.js
Python将多个excel文件合并为一个文件
2018/01/03 Python
python实现AES加密与解密
2019/03/28 Python
Python批量生成幻影坦克图片实例代码
2019/06/04 Python
Flask框架搭建虚拟环境的步骤分析
2019/12/21 Python
python读取文件指定行内容实例讲解
2020/03/02 Python
Python return语句如何实现结果返回调用
2020/10/15 Python
Html5监听手机摇一摇事件的实现
2019/11/07 HTML / CSS
goodhealth官方海外旗舰店:新西兰国民营养师
2017/12/15 全球购物
电子专业自荐信
2014/07/01 职场文书