使用Redis实现抢购

此文转自:https://my.oschina.net/crazymus/blog/533371?p=2

补充:redis中所有单个命令的执行都是原子的,所以文章中提到pop是原子的,llen也是原子的,但是第一种处理方式如果A线程通过llen获取库存,在lpush之前可能B线程也通过llen获得同样的库存,这样执行下来会出现超抢的情况,虽然redis是单线程的但支持多线程模式操作。

抢购是如今很常见的一个应用场景,主要需要解决的问题有两个:

1 高并发对数据库产生的压力

2 竞争状态下如何解决库存的正确减少(“超卖”问题)

对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。重点在于第二个问题,我们看看下面一种常规的实现代码:

<?php 

require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(
	'scheme' => 'tcp',
	'host'   => '127.0.0.1',
	'port'   => '6379'
));

//redis 登录 
$redis->auth('123456');

//库存
$num = 10;

//用户id
$user_id = $_SESSION['user_id'];

//检查库存 
$len = $redis->llen('order:1');
if($len >= $num){
	exit('已经抢光了');
}

//把抢到的用户存入到列表中  
$result = $redis->lpush('order:1',$user_id);
if($result){
	echo '抢到了';
}

?>

如果代码正常运行,列表order:1中最多只能存储10个用户的id,因为库存只有10个。

然而,在使用Apache AB工具模拟很多用户并发请求时,最后发现order:1中总是超过10个用户,也就是出现了“超卖”。

问题就出在这一段代码:

//检查库存 
$len = $redis->llen('order:1');
if($len >= $num){
	exit('已经抢光了');
}

在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过,这三个用户都能抢购成功。而实际上只有一件库存可以抢了。

在高并发下,很多不是问题的,都成了问题。要解决“超卖”问题,核心在于保证检查库存时的操作是依次执行的,形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。

我们需要使用Redis的原子操作来实现这个“单线程”。首先我们把库存存在goods:1这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅代表一件库存。抢购开始后,每到来一个用户,就从goods:1中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:

<?php 
//抢购 
require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(
	'scheme' => 'tcp',
	'host'   => '127.0.0.1',
	'port'   => '6379'
));

$redis->auth('123456');

//用户ID
$user_id = $_SESSION['user_id'];

$check = $redis->lpop('goods:1');
if(!$check){
	exit('抢光了');
}

$result = $redis->lpush('order:1',$user_id);
if($result){
	echo '抢购成功';
}

?>

用户抢购成功后,我们将用户ID存入了order:1列表中。接下来我们可以引导这些用户去完成订单的其他步骤,这里才涉及到与数据库的交互。最终只有很少的人走到这一步,也就解决的数据库的压力问题。

为了检测实际效果,我使用Apache AB工具模拟10、20、1000个用户并发进行抢购,经过大量的测试,最终抢购成功的用户始终为10,没有出现“超卖”。

---------分割线----------

后话:上面只是简单模拟高并发下的抢购思路,真实场景要比这复杂很多,比如双11活动远远比这更复杂多,以下思路可供参考:需要一个排队队列(比如:queue:1,以user_id为值的列表)和抢购结果队列(比如:order:1,以user_id为值的列表)及库存队列(比如上面的goods_store:1)。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在则已抢购,否则未抢购,接着执行库存减1,写入数据库,将此user_id用户同时也进入结果队列。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值