PHP编程意识进阶-原子性

代码均为伪码

你接了个任务,写个商城秒杀。你心想,这不是手到擒来。
热点数据先预热进缓存,秒杀时走缓存,轻轻松松搞定。
于是,你在后台编辑商品库存时,这样写:

$skuId = 1;
$stock = 100;
Cache::set('sku-' . $skuId, $stock);

秒杀逻辑你这样写:

$buyCount = $request->post('buy_count');
$skuId = $request->post('sku_id');

if (($stock = Cache::get('sku-' . $skuId)) < $buyCount) {
    throw new \Exception('库存不足');
}

//购买逻辑

Cache::set('sku-' . $skuId, ($stock - $buyCount));

结果上线时,你发现超卖了,你只好提桶跑路。聪明的你肯定发现了, 无论是php-fpm还是php-cli模式,worker 进程肯定不止一个,你的项目能支持的并发肯定也不止一个,多个并发过来,这段逻辑同时执行时,扣除逻辑还没执行到,多个并发的判断逻辑已经执行完了,结果就是好好的营销活动,被我们弄砸了。这怎么破?

问题的症结在于判断和扣除逻辑本应该是原子性的,而你分开写了,假设 Cache 用到的适配器是 redis 而不是 file或者database啥的。你应该把判断和扣除封装成 lua script,让 redis server保证其原子性。

$script = <<< EOF
    if (redis.call('get', '$key')>=$buyCount) then
        redis.call('decr', '$key', $buyCount)
    end
EOF;

Redis::eval($script);

lua 语法不一定正确,凭印象写的

这样就保证了整个操作的原子性,其实原子性是一种方法论,他贯穿在整个编程世界中,包括 mysql 判断和扣除问题,怎么样保证其原子性,分布式锁还是利用数据库锁都。多思考原子性的问题,你的编程技能一定可以更上一层楼。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

听起来好高大上,我连SKU都不会写

6个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!