如果电子商务网站的热门商品每秒收到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的锁,这是一个意外的行为。

名称未設定.002.jpeg

在这种情况下,最好是每次都延长锁定的过期时间。

雷迪森

上述的功能已经有一个可以方便实现的仓库存在。
可以使用redisson来简单实现。
https://github.com/redisson/redisson

广告
将在 10 秒后关闭
bannerAds