Redis防止超卖的两种实现
2020-08-12 本文已影响0人
莴牛
环境准备:mac,php7,redis,laravel
新建个数据库表用来测试
CREATE TABLE `goods_sold` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`goods_id` bigint(20) DEFAULT NULL,
`num` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#插入一条数据
INSERT INTO `goods_sold` (`goods_id`, `num`) VALUES (1, 100);
-
使用redis watch 和 事务
public function OverSoldFunctionWithRedisTransaction()
{
// todo 从数据库获取库存 这里忽略,直接写死一个 提前取出放入缓存中
$total = 100;
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'watchKey';
$watchKey = $redis->get($key);
if ($watchKey < $total) {
$redis->watch($key);
// 开启redis事务
$redis->multi();
// sleep(1);
$redis->set($key, $watchKey + 1);
$transResult = $redis->exec();
if ($transResult) {
GoodsSold::find(1)->decrement('num');
exit('抢购成功');
} else {
exit('抢购失败,下次再来');
}
} else {
exit('商品已抢光');
}
}
ab压测看实际效果
ab -n 1000 -c 100 -k http://laravel.tyl.com/over/sold
压测数据
- 重复测试需要删除redis值、同时更新数据库的库存总数和代码中一致(100)
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> get watchKey
"100"
127.0.0.1:6379> del watchKey
(integer) 1
127.0.0.1:6379> get watchKey
(nil)
127.0.0.1:6379>
-
使用List存储(队列)
#先使用第一步取出库存放入队列中
/**
* 取出库存放入队列中
*/
public function PushQueue()
{
$data = GoodsSold::find(1);
$total = $data->num;
if ($total > 0) {
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$queueKey = 'queueKey';
for ($i = 1; $i <= $total; $i++) {
$redis->lPush($queueKey, $i);
}
}
}
/**
* 超卖实现二
*/
public function OverSoldFunctionWithRedisQueue()
{
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$queueKey = 'queueKey';
if ($redis->rPop($queueKey)) {
GoodsSold::find(1)->decrement('num');
exit('抢购成功');
} else {
exit('商品已抢光');
}
}
ab压测看实际效果
ab -n 1000 -c 100 -k http://laravel.tyl.com/over/sold/2
总结
- 方案一和二都需要将库存数据提前同步到redis当中
- 方案二使用链表效果更好