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

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

串行化实现

思路

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

代码

?php//连接本地的Redis服务$redis=newRedis();$redis-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"执行减库存逻辑br";echo"创建订单br";$redis-del("product_lock_1");//删除锁break;}else{if($i=2){file_put_contents("","失败\n",FILE_APPEND);exit("失败");}$i++;}}

压力测试

结果

压力测试结果

日志结果

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

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

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

高并发方案

思路

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

代码

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

压力测试

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

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

结果

压力测试结果

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

订单数量

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