php redis 秒杀
鉴于网上很多关于redis的,用的函数也不一样,很多函数重复的。我自己参考了几个,并实际测验给大家分享一下。
test库 goods表
CREATE TABLE `goods` (
`good_id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) DEFAULT NULL,
PRIMARY KEY (`good_id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
基于redis分布式锁实现“秒杀
是短时间内多个用户“争抢”资源,秒杀就是多个线程对资源进行操作,所以实现秒杀,就必须控制线程对资源的争抢,既要保证高效并发
我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1。现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,500个线程秒杀第二个商品。我们来根据这个简单的业务场景来解释一下分布式锁。
通常具有秒杀场景的业务系统都比较复杂,承载的业务量非常巨大,并发量也很高。这样的系统往往采用分布式的架构来均衡负载。那么这1000个并发就会是从不同的地方过来,商品库存就是共享的资源,也是这1000个并发争抢的资源,这个时候我们需要将并发互斥管理起来。这就是分布式锁的应用。
而key-value存储系统,如redis,因为其一些特性,是实现分布式锁的重要工具。
先普及一下用到的知识点
llen
获取list的元素个数
lpush
添加一个或多个元素插入到list的头部
$redis->get("num")
(一开始默认为0,证明还没有人参与抢购。后续逐渐自增)
$redis->lpush("user",1);//记录抢购成功的用户id
$redis->lpush("user",2);//记录抢购成功的用户id
var_dump($redis->lrange('user',0,-1));
array(2) { [0]=> string(1) "2" [1]=> string(1) "1" }
开启wins本地redis服务端
redis-server.exe redis.windows.conf
开启客户端
D:\download\install\redis\redis>redis-cli.exe
127.0.0.1:6379> auth "12345"
lpop
描述:返回和移除列表的第一个元素
参数:key
返回值:成功返回第一个元素的值 ,失败返回false
//获取列表中所有的值
$list = $redis->lrange('list', 0, -1);
自增
$redis->incr('num')
使用ab进行压力测试详解
https://blog.csdn.net/qiu1988yang/article/details/74940914
测试总次数为1000,并发数为100(相当于100个用户同时访问,他们总共访问1000次)。我们输入DOS命令
ab -n 1000 -c 100 http://www.testone.cn/redis.php
在这个测试的过程中,我一边遍用ab测试,一边访问浏览器,因为超卖有时候不会那么明显发生,再一个如果超过ab测试范围(我是用本地phpStudy自带的apache测试的)就无法访问了,造成测试失败
测试总次数为1000,并发数为100(相当于100个用户同时访问,他们总共访问1000次)
第一种
推荐方式 php+redis乐观锁(如果你不了解redis的各个函数意思,请不要乱排序这几行代码,因为我试着改了几行发现数据库直接变成-9,所以你看顺序的话先不要变。以实现功能为主,以后慢慢理解)
<?php
$pdo=new PDO("mysql:dbname=test;host=localhost","root","root");
$pdo->query('SET NAMES utf8');
$redis=new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('12345');
$uid=md5(mt_rand(1,1000000));//模拟生成用户id
// $redis->lpush("user",1);//记录抢购成功的用户id
// $redis->lpush("user",2);//记录抢购成功的用户id
// var_dump($redis->lrange('user',0,-1));
//array(2) { [0]=> string(1) "2" [1]=> string(1) "1" }
$num=$redis->get("num");
if($num>=3){
exit("抢购结束");//假设只有3个库存量
}else{
$redis->watch('num');//监听key的变化,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
//开始事务
$redis->multi();
$redis->incr('num');
$result=$redis->exec(); //返回true表示执行成功,nil表示事务被中断
if($result)
{
$stmt = $pdo->exec("update goods set num=num-1 where good_id=1");
if($stmt){
echo "抢购成功";
$redis->lpush("user",$uid);//记录抢购成功的用户id
//自行写各种业务处理。。。
}
}
}
第二种
行级锁
<?php
$pdo=new PDO("mysql:dbname=test;host=localhost","root","root");
$pdo->query('SET NAMES utf8');
$redis=new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('12345');
$num=$redis->get("num");
if($num<=3)//假定库存只有3个
{
$sql = "select * from goods where good_id=1";
$stmt = $pdo->query($sql);
$res=$stmt->fetch(2);
if($res['num'])
{
var_dump(1);
$redis->incr('num');
$stmt = $pdo->exec("update goods set num=num-1 where good_id=1");
//原作者写的是下面这个,但是我实际测试的时候这里不加行锁也没有问题。用的上面这句。不过具体还要参照自己的业务情况去分析。
$stmt = $pdo->exec("update goods set num=num-1 where good_id=1 for update"");
echo "抢购成功";exit;
//自行写各种业务处理。。。
}
}else{
var_dump(2);
echo "抢购结束";exit;
}
如果对行锁不懂可参考这个
MySQL的for update使用(行级锁)
https://blog.csdn.net/qq_27037443/article/details/102984108
第三种
文件锁不推荐就不说了
第四种
直接上网址吧https://www.cnblogs.com/yehuisir/p/10776172.html
这个里面文章写得思路很详细,但是我没有实际测试。
第五种
将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false(未测试)
其他的包括很多种其他方式,我没有测试就不说了。
参考网址:
MySQL的for update使用(行级锁)
https://blog.csdn.net/qq_27037443/article/details/102984108
https://www.cnblogs.com/jokmangood/p/11737169.html
php操作redis常用方法
redis本地服务启动和桌面客户端安装(Windows)
https://blog.csdn.net/liuchang19950703/article/details/104372940/
范例:
php结合redis实现高并发下的抢购、秒杀功能
https://www.cnblogs.com/huanglei559/p/11045516.html
PHP操作Redis数据库常用方法
https://www.cnblogs.com/lxwphp/p/10597809.html
Php+redis+锁机制实现高并发秒杀抢购解决方案
https://blog.csdn.net/qq_39418742/article/details/105974159
PHP使用Redis的Transaction(事务)命令
https://blog.csdn.net/weixin_43910923/article/details/88693412