如果电子商务网站的热门商品每秒收到100万个请求的话
卖得太多了 dé duō le)
在电子商务网站中,经常会出现多个购买请求同时涌入热门商品的情况。
目前的系统通常会使用多个服务器来分散处理请求,
但如果出现多个购买处理同时运行在相同的库存数据上会发生什么?
产品库存数量:100
服务器1和服务器2同时获取库存数量。
服务器1.获取库存数量() -> 返回100
服务器2.获取库存数量() -> 返回100
进行购买处理
服务器1.更新库存数(100 – 1)-> 返回99
服务器2.更新库存数(100 – 1)-> 返回99
换句话说,实际上卖出了两个商品,但根据程序的处理,库存只减少了一个。如果反复这样操作,就会发生商品超卖的情况,客户购买了商品但最终无法供应。
上锁
为了防止上述过度销售,使用锁可以解决这个问题。在这里,考虑到性能问题,我们将考虑使用redis实现锁的方法,而不是像MySQL等数据库的锁机制。
原始销售逻辑
$stock = $redis->getStock($productCode);
if ($stock > 0) {
$this->makePurchase();
$stock -= 1;
$redis->setStock($productCode, $stock);
}
这是更新库存的最简单的代码。
对于单个服务器的情况没有问题,但是对于多服务器的情况和并发处理,会出现超卖的问题。
给锁上
$lockKey = 'lockKey';
$lock = $redis->setnx($lockKey, 'lock'); # ロックをかけてみる return bool
if (!lock) {
# ロックがすでに存在していて、購入を進めるべきではない
return "errorCode";
}
$stock = $redis->getStock($productCode);
if ($stock > 0) {
$this->makePurchase();
$stock -= 1;
$redis->setStock($productCode, $stock);
}
$redis->delete($lockKey);
即使存在多个线程,只有第一个线程才能更新库存,从而确保库存数量的一致性。
然而出现了问题。 ‘ér .)
只有执行$redis->setnx($lockKey, ‘lock’)成功的线程才能获取到锁,
其他线程将返回错误。尽管使用了并发处理,但只能像单线程一样执行。
这会导致处理变慢。
如果$redis->delete($lockKey);在执行之前发生错误,无法释放锁定,
将会发生死锁,后续的线程将无法再次锁定或解锁,
购买功能当然也无法正常运行。系统功能将会崩溃。
添加try finally。
$lockKey = 'lockKey';
try {
$lock = $redis->setnx($lockKey, 'lock'); # ロックをかけてみる return bool
if (!lock) {
# ロックがすでに存在していて、購入を進めるべきではない
return "errorCode";
}
$stock = $redis->getStock($productCode);
if ($stock > 0) {
$this->makePurchase();
$stock -= 1;
$redis->setStock($productCode, $stock);
}
} finally (\Throwable $th) {
$redis->delete($lockKey);
}
只要在线程中无论发生什么情况,最终都可以释放锁。然而,如果在执行$redis->delete($lockKey);之前进程被杀,或者服务器宕机,锁将保持未释放状态。
在中文中添加过期时间到锁上
try {
$lock = $redis->setnx($lockKey, 'lock'); # ロックをかけてみる return bool
$redis->expired($lockKey, 60);
// ...
} finally (\Throwable $th) {
$redis->delete($lockKey);
}
只要系统崩溃,时间到了锁就会自动解除。
不过,如果进程在执行$redis->expired($lockKey, 60);之前被终止,就会发生死锁。
try {
$lock = $redis->setnx($lockKey, 'lock', 60); # ロックをかけてみる return bool
// ...
} finally (\Throwable $th) {
$redis->delete($lockKey);
}
如果你设置了一个过期时间,那么当你锁上时钟时,我会帮你设置一个过期时间。
如果你能做到这一点,代码在某种程度上就可以在生产环境中使用了。
但是,如果请求量真的很大的话,可能会有问题。
由于线程的执行时间不同,因此线程1有时会解锁线程2的锁,这是一个意外的行为。
在这种情况下,最好是每次都延长锁定的过期时间。
雷迪森
上述的功能已经有一个可以方便实现的仓库存在。
可以使用redisson来简单实现。
https://github.com/redisson/redisson