PHP分布式锁超卖方案以及高并发优化

2022-10-11 20:41:43 131 0
魁首哥

在PHP的生态中 ,是通过多进程的方式去优化程序性能的。在单机架构情况下防止超卖不像JAVA那样可以使用自身的锁机制实现。需要借助第三方程序来实现,如:数据库、Redis等。

接下来我们通过一个基于Redis实现的分布式锁来看下思路。

串行化实现

思路

一个商品建立一个分布式锁的key,用户请求每次购买商品,都需要先获取这个key,然后执行创建订单的逻辑;如果key获取不成功,则循环3次,三次都失败则退出。

代码

ceshi.php

 connect('redis', 6379);
$i=0;
while (true) {
    // 这里使用 redis 的set方法,增加nx、ex 
    // nx:在key存在时返回false;不存在时返回设置成功,并且返回true
    // ex:设置KEY过期时间,redis中单个命令是原子性操作,过期时间是为了防止死锁
    $re = $redis->set("product_lock_1", 2, ['nx', 'ex'=>5]);
    if ($re) {
        echo "执行减库存逻辑
"; echo "创建订单
"; $redis->del("product_lock_1"); // 删除锁 break; } else { if ($i >= 2) { file_put_contents("product.txt", "失败\n", FILE_APPEND); exit("失败"); } $i++; } }

压力测试

结果

压力测试结果

日志结果

100的并发,请求1000次,失败请求30次,成功请求中有22次未获得锁而失败。

上面的例子,我们仔细分析下,接口请求需要先获取锁,然后才能执行后面的逻辑。 如果一个php-fpm进程获取得到了这个锁,在锁释放之前,其他php-fpm进程只能等待 。这是一个串行化的执行过程,并不能善用多核CPU。所以接口的性能偏低。

接下来我们来看下优化后的方案。

高并发方案

思路

该方案的核心是将商品的库存同步到Redis,Redis通过 decr 原子性扣减商品库存,然后执行商品的创建订单操作。有两个后台任务,一个查看商品库存是否真正用完了,如果完了就将状态更新到Redis中;另一个不停地将可用的库存数量(商品库存 – 支付未超时的订单库存)更新到Redis中。

代码

 connect('redis', 6379);
// 这一步是用来给后台任务的,如果商品库存真正为0了,
// 通过设置这个redis字段,直接给用户返回商品已售完.
$is_end = $redis->exists("product_lock_1_end");
if($is_end) {
    // echo "库存真正不足";
    return;
}
// 这一步原子性操作,将库存是否足够的判断交给redis
// 这里的库存可以通过后台任务,将可用的
//   库存数量(商品库存 - 支付未超时的订单库存)
// 更新到Redis中。
$stock = $redis->decr("product_lock_1_stock");
if ($stock < 0) {
    // echo "库存==冻结数量";
  	// 将扣减的数量加回去
    $redis->incr("product_lock_1_stock");
    return;
}
// echo "执行减库存逻辑
"; // echo "创建订单
"; $redis->incr("order_number");

压力测试

测试之前在redis里面执行下面命令,设置商品库存。

set product_lock_1_stock 100

然后再执行压力测试命令:

ab -n 1000 -c 100

结果

压力测试结果

100的并发,1000次请求,失败为0。

订单数量

每个订单一个商品,并未发生超卖现象。

收藏
分享
海报
0 条评论
131
上一篇:php中通过Hashids将整数转化为唯一字符串 下一篇:一位笔名叫如风的程序员讲述他从自学php到学成出山的过程

本站已关闭游客评论,请登录或者注册后再评论吧~

忘记密码?

图形验证码