PHP对于超卖秒杀问题的处理方案
1 mysql悲观锁
BEGIN;
SELECT * FROM 'TEST' WHERE id = 1 FOR UPDATE;
COMMIT;
在数据处理过程中,将数据锁定,禁止此操作之外的任何数据操作,保证数据处理唯一性,需要数据库层面提供锁机制。
缺点:因为一次只能处理一条数据,所以多用户同时操作时,会出现等待问题,用户体验感极差。
2 mysql乐观锁
SELECT version FROM test;
#省略,这里写业务逻辑
UPDATE test SET number=number-1,version=version+1 WHERE id=1 AND version=#{version};
在代码层面通过版本号保证数据不会冲突,处理数据时先查询得到version,修改数据时再将version对比,如果两者相同,则说明数据在此期间没有被修改过。
缺点:还是需要数据库对所有请求进行处理,其中还包括了数据查询修改,所以很消耗数据库资源。
3 PHP+队列
系统将请求不作处理,全部存入队列中,在特定时间下,通过脚本从队列一条一条提取请求,并对其进行数据处理。
缺点:因为是一条一条处理,所以并发性不高,处理数据缓慢。
4 PHP+Redis分布式锁
#查询job是否存在
redis> EXISTS job
(integer) 0
#设置job
redis> SETNX job "php"
(integer) 1
#尝试覆盖job,失败
redis> SETNX JOB "python"
(integer) 0
#查看job
redis> GET job
"php"
SETNX 与 SET 是有区别的,前者只能存储一次,后者能无限覆盖存储
Redis分布式锁就是借此实现的
$expire = 10; //有效时间
$key = '123456';
$value = time() + $expire;
$status = true;
while($status)
{
//当有1000条数据进行到此时,只有一条数据会被保存成功,其余都是失败。
$lock = $redis->setnx($key, $value);
//失败会进入循环
if(empty($lock))
{
$value = $redis->get($key);
if($value < time())
{
$redis->del($key);
}
//成功则进入业务处理
}else{
$status = false;
//这里写业务逻辑
}
}
缺点:还是并发性不高的问题。
5 PHP+Redis乐观锁
<?php
$redis = new Redis();
$redis -> connect("127.0.0.1",6379);
//乐观锁监视number,若数据处理时number变化,事务机制将回滚数据
$redis -> watch('number');
$number = $redis -> get('number');
$n=100;
if($number >= $n){
exit('秒杀结束');
}
//开启事务
$redis -> multi();
//将key中储存的数字值增一,如果key不存在,那么key的值会先被初始化为0,然后再执行INCR操作。
$redis -> incr('number');
$res = $redis -> exec(); //成功1失败0
if($res){
//秒杀成功
incTude ' db.php';
$sq1="update products set store=store-1 where id=1";
if ($mod->exec($sq1)){
echo "秒杀完成" ;
}
}e1se{
exit('抢购失败');
}
相比前几个,有以下优点:
1 选用内存数据库抢购,速度极快。
2 速度快并发自然不存在
3 悲观锁会迅速增加系统资源
4 比队列强,队列会使你的内存数据库资源瞬间爆棚。