持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
秒杀场景
- 拼夕夕的苹果手机618有秒杀活动,可能手机只有1000台,但同时进入的流量可能是成百上千万。如果没有防护措施,就会出项库存1000台,买到的人远远超出1000人,那这样就造成很严重的问题了。
- 12306抢票,一列火车的票是有限的,库存一份,并发流量非常多,大家都读相同的库存。但是火车的座位就那个些,如果发生了超卖,那到时候火车都装不下那么多人。
- 稀土掘金的福利兑现功能,每周都会开放特定惊喜好物打折售卖,但是库存也是有限的,如果没有采取防范措施让掘友们一哄而上抢过了,那没货发怎么办呢?
防护措施
- 使用redis实现分布式锁。
- 使用redis实现队列。
redis实现分布式锁
直接上代码
<?php
namespace app\lib\redis;
class RedisLock
{
private $acquireTimeout = 2;
protected $connection;
public function __construct()
{
$config = [
'host' => VITEC_CONFIG['aliyunRedisHost'],
'port' => VITEC_CONFIG['aliyunRedisPort'],
'password' => VITEC_CONFIG['aliyunRedisAuth'],
'dbindex' => VITEC_CONFIG['redisSelect'],
];
$redis = new \Redis();
if ($redis->connect($config['host'], $config['port']) === false) {
throw new RedisException($redis->getLastError(), 500);
}
if (!empty($config['password'])) {
if ($redis->auth($config['password']) === false) {
throw new RedisException($redis->getLastError(), 500);
}
}
if (!empty($config['dbindex'])) {
if ($redis->select($config['dbindex']) === false) {
throw new RedisException($redis->getLastError(), 500);
}
}
$this->connection = $redis;
}
public function redisLock($key, $expireTime = 2, $isNegtive = false)
{
if ($isNegtive) {
while ($this->connection->setnx($key, time() + $expireTime) == 0) {
if (time() > $this->connection->get($key) && time() > $this->connection->getSet(
$key,
time() + $expireTime
)) {
break;
} else {
usleep(100);
}
}
return true;
} else {
$res = $this->connection->setnx($key, time() + $expireTime);
if ($res) {
return true;
}
return false;
}
}
public function delRedisLock($key)
{
$this->connection->del($key);
}
- 以上代码分别实现了乐观锁和悲观锁。
- setnx的作用为如果不存在则设置,如果任务标识key的值不存在且设置成功则函数返回1,失败则返回0.
- 乐观锁:我只尝试取一次锁,如果使用setnx设置锁,成功返回true,失败返回false。
- 悲观锁:循环阻塞式地使用setnx去取锁,如果取不到锁会判断锁是否超时,key对应的值就是超时时间,可以与当前时间进行对比,如果小于当前时间则表示锁超时,同时执行getset方法再去获取旧值设置新的超时时间并获取锁,执行成功返回true,执行失败则睡个一段时间继续循环去取锁。
redis队列
这种方式简单很多了,我们先把库存从数据库中读出来写到redis的列表里面。然后开始秒杀的时候让用户并发请求,我们就直接从redis的队列中把商品拿出来就可以了,因为redis的所有操作都是原子性的,即使有多个操作同时到达也是按照顺序来执行的,那这样就有效的避免了超卖的问题。
从数据库获取库存到redis
<?php
$store=5000;
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
for($i=1;$i<=$store;$i++){
$redis->lpush('good_ids',$i);
}
return true;
秒杀时从redis队列获取库存
<?php
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$count=$redis->lpop('good_ids');
if(!$count){
return false;
}
//获取到了商品的id继续执行后面的售卖逻辑
.....
return true;
谢谢观看!streetlamp敬上!