php和redis实现秒杀活动的流程
时间:2022-07-27
本文章向大家介绍php和redis实现秒杀活动的流程,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
1 说明
前段时间面试的时候,一直被问到如何设计一个秒杀活动,但是无奈没有此方面的实际经验,所以只好凭着自己的理解和一些资料去设计这么一个程序
主要利用到了redis的string和set,string主要是利用它的k-v结构去对库存进行处理,也可以用list的数据结构来处理商品的库存,set则用来确保用户进行重复的提交
其中我们最主要解决的问题是
-防止并发产生超抢/超卖
2 流程设计
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日志的记录结果:
#第2种情况 5000并发下 用客户端的test2()去执行
ab -n 5000 -c 1000 www.hello.com/flash-sale.php
log日志的记录结果:
#第3种情况 500并发下 用客户端的test()去执行
ab -n 500 -c 100 www.hello.com/flash-sale.php
log日志的记录结果:
#第4种情况 5000并发下 用客户端的test()去执行
ab -n 5000 -c 1000 www.hello.com/flash-sale.php
log日志的记录结果:
5 总结
我们从日志中可以很明显的看出第3、4中情况下,可以保证商品的数量总是我们设置的库存值10,但是在情况1、2下,则产生了超卖的现象
redis来控制并发主要是利用了其api都是原子性操作的优势,从checkStock和checkStockFail中可以看出,一个是直接decr对库存进行减一操作,所以不存在并发的情况,但是另一个方法是将库存值先取出做减一操作然后再重新赋值,这样的话,在并发下,多个进程会读取到多个库存为1的值,因此会产生超卖的情况
- 分析函数之窗口子句(r4笔记第3天)
- node模块加载层级优化
- 使用ajax方法实现form表单的提交
- 翻译:如何使用CSS实现多行文本的省略号显示
- node中子进程同步输出
- Java开源博客My-Blog之docker容器组件化修改
- 几个行列转换的实用小例子(r4笔记第2天)
- History API与浏览器历史堆栈管理
- node中创建服务进程
- 数据挖掘工程师:如何通过百度地图API抓取建筑物周边位置、房价信息
- crontab导致CPU异常的问题分析及处理(r3笔记第100天)
- 短信接口被恶意调用(二)肉搏战-阻止恶意请求
- 关于首屏时间采集自动化的解决方案
- javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 详说C#中的结构struct
- c#结构体总结
- 跨域问题(CORS / Access-Control-Allow-Origin)
- C#中的结构体与类的区别
- c#结构体与类的区别,及使用技巧 C#中的结构体与类的区别
- hikvision SDK使用(转)
- 《Scikit-Learn与TensorFlow机器学习实用指南》 第11章 训练深度神经网络(上)
- 海康SDK开发步骤
- 《Scikit-Learn与TensorFlow机器学习实用指南》 第11章 训练深度神经网络(下)
- applet跨域访问的安全性问题(java.security.AccessControlException:access denied)
- JSON解析问题:net.sf.json.JSONException: There is a cycle in the hierarchy!
- 海康相机SDK联合c++标定
- 开发一个Node命令行小玩具全过程--高颜统计工具
- 消息未读之点不完的小红点(Node+Websocket)
- pkg版本规范管理自动化最佳实践