并发问题的解决思路

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

场景

在平时的工作中,常常会有要对数量有精确控制的业务需求。比如商品库存量、奖品数量、报名人数限制等等,这些场景往往并发较高。拿减商品库存场景来说,如果控制不好,很有可能出现超卖的现象。

方法一:使用文件排它锁

flock 函数用于获取文件的锁,这个锁同时只能被一个线程获取到,其它没有获取到锁的线程,要么阻塞,要么获取失败。

在获取到锁的时候,先查询库存,如果库存大于 0,则进行下订单操作,减库存,然后释放锁。

方法二:使用悲观锁

InnoDB 存储引擎支持行级锁,当某行数据被锁定时,其它进程不能对这行数据进行操作。

在 InnoDB 存储引擎中,当我们执行 update 语句的时候,会自动为该行加一个行锁。


update goods set repertory = repertory - 1 WHERE goods_id = 1234 and repertory > 0

复制代码

另外一种方式,使用 for update 显式加锁,使用 for update 需要满足两个条件:

  • 引擎是 InnoDB

  • 操作需要在事务中(begin/commit)


begin;

select repertory from goods where goods_id = 1234 for update;

if (repertory > 0) {

update goods set repertory = repertory - 1 where goods_id = 1234;

}

commit;

复制代码

悲观锁采用的是「先获取锁再访问」的策略,但是这种策略会增加数据库的负担,且有可能会导致死锁。在实际生产中,对于高并发的场景并不会使用悲观锁,因为当一个事务锁住了记录,那么其他事务都会发生祖泽,大量的事务阻塞可能会拖垮整个系统。

方法三:使用乐观锁

乐观锁是总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和 CAS 算法实现。

扫描二维码关注公众号,回复: 13168930 查看本文章

我们以版本号方法为例。

版本号机制一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。


select version as old_version from goods where goods_id = 1234;

update goods set repertory = repertory - 1, version = version + 1 where goods_id = 1234 and repertory = 0 and version = ${version};

复制代码

假设此时有 3 个库存,此时 version = 1,6 个请求都过来了,都执行上述语句,查到的 version 都为 1,但是肯定有一线程先执行成功,此时 version = 2,那么其他 update 语句发现 version 不等于上次 select 出来的 version,说明这次 version 被其他请求修改过了,就会放弃这次 update。

方法四:使用队列

我们可以使用 Redis 队列方案来解决高并发问题,我们用一个队列来接收请求,然后再一个一个将数据 pop 出来进行处理。

我们用订单号、用户 ID、商品 ID、购买数量等,生成一个唯一 ID,然后将该值 push 到订单队列中,再启动其他线程消费该数据。


lpush repertory orderId_goodsId_accountId_count

复制代码

方法五:使用 Redis

在高并发情况下,频繁的读库写库,会造成严重的性能问题。我们可以使用 Redis。

Redis 的操作都是原子性的,可以将商品的库存写入 Redis 中。下单之前对库存进行 DECR 操作,如果返回结果 >=0 则可以正常下单、减库存。否则提示库存不足。


172.18.4.181:7006> set repertory 2

OK

172.18.4.181:7006> DECR repertory

(integer) 1

172.18.4.181:7006> DECR repertory

(integer) 0

172.18.4.181:7006> DECR repertory

(integer) -1

复制代码

参考文档

猜你喜欢

转载自juejin.im/post/7019690466828353544