在电子商务发达的今天,各种秒杀,抢购活动的场景不少,如何解决高并发下出现的订单超发情况呢?
在常规情况下,我们会根据用户提交的请求去查询商品库存,如果库存小于0则订单生成失败。但是这种情况下经常了订单需要的数量超过了库存数量,即出现负库存。
先讨论常规场景测试:
我们在redis中设置一个库存表,就是个简单的string类型, 用以标示库存即可。
set inventory 100
使用多进程测试如下:因为我的AB测试不能用。所以使用多进程的方式进行
//register a signal pcntl_signal(SIGCHLD, SIG_IGN); $times = 200; while ($times-- > 0) { $pid = pcntl_fork(); if ($pid > 0) { } else { order(); die; } } sleep(20); function order() { $conn = new Redis; //connect redis $conn->connect("127.0.0.1", 6379); $inventory = $conn->get('inventory'); //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发 usleep(50000); if ($inventory > 0) { $conn->decr("inventory"); } else { echo "抢购失败!"; } die; }
执行结果变为了负数:
如何解决这个问题呢? 熟悉redis 的同学都知道 redis 支持事务,我们加个事务试试。
<?php //register a signal pcntl_signal(SIGCHLD, SIG_IGN); $times = 200; while ($times-- > 0) { $pid = pcntl_fork(); if ($pid > 0) { } else { order(); die; } } sleep(20); function order() { $conn = new Redis; //connect redis $conn->connect("127.0.0.1", 6379); $inventory = $conn->get('inventory'); $conn->multi(); //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发 usleep(50000); if ($inventory > 0) { $conn->decr("inventory"); $conn->exec(); } else { echo "抢购失败!"; } die; } ?>
同样结果还是会变成负数。
接下来可以使用redis 的watch 实现一个乐观锁来试试
<?php //register a signal pcntl_signal(SIGCHLD, SIG_IGN); $times = 200; while ($times-- > 0) { $pid = pcntl_fork(); if ($pid > 0) { } else { order(); die; } } sleep(20); function order() { $conn = new Redis; //connect redis $conn->connect("127.0.0.1", 6379); do { //watch inventory $conn->watch('inventory'); $inventory = $conn->get('inventory'); //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发 usleep(50000); if ($inventory <= 0) { echo "抢购失败!"; break; } $conn->multi(); $conn->decr("inventory"); } while ($conn->exec()); die; } ?>
执行结果:
大致场景就是这样的, 如果是抢购,每次只抢购一个 可以往队列里添加库存个商品的1 队列, 如图:
当用户每次抢购抛出一个队列,然后再去减少真正的库存即可。 当然真实的场景要稍复杂一些, 不过原理就是这样的。
成功!