浅谈高并发场景开发中涉及的锁

我们在实际开发中,除了在互联网或者电商公司可能会遇到高并发的场景,其他公司大概率是没有高并发场景的。以我个人的经历,在金融公司的一线,一般的单个应用接口锁支持的并发在20左右,然后运维部署多台机器,例如20台,前端再用F5或者nginx做个负载均衡,并发量就支持在400左右。

这里谈一谈,我们解决高并发的思路。

高并发最重要的事:资源不能超支。(例如:商品超卖)

第一境界:采用悲观锁。

悲观锁,大家应该都无数次听过这个名词了,不要以为是什么高大上的概念。其实在数据库层的实现就是 select product_name, price,stock from product_table where id = #{ id } for update 这个for update(Oracle 数据库有时也用 for update nowait) 就是对针对查询出来的这行数据进行锁定,其他事务不能再对其进行读写,这样就避免了高并发场景下的数据不一致。即使多线程处理数据也不会冲突。问题是解决了,但是效率呢?如果是传统型的公司倒可以满意,但是随着业务量增加,未来肯定需要改善。所以这个方案,基本在实际的电商公司,基本也就停留在理论阶段。

第二境界:采用乐观锁。

乐观锁既然是乐观,自然就要摆脱数据库锁的方案。不使用数据库锁和不阻塞线程并发的方案。以购买商品为例,改进后就是事先保存商品数据旧的库存,每次去更新库存商品时,如果旧值与当前库存一致,表示数据没有被修改过,否则就认为数据已经被修改了,当前计算不能修改任何数据。

这个理念就是Java中多线程的方案 CAS Compare and Swap  这个方案也存在一个常见的ABA问题,即在短时间内一个线程其实已经有过数据修改了,只是时间很快,又恢复回来了。另一个线程再去读取时,发现值没有变,就认为当中没有变化。但其实在那短暂的时间内,商品是存在总价问题的。所以我们继续改进的方案为:增加每次操作的版本号,类似于序列,只能增加,不能递减。

例如:update product_table set stock = stock - #{quantity}, version = version +1 where id ={id} and version = #{version}

第三境界:可重入锁机制

理论上第二境界,已经可以搞定了。但实际是,这种乐观锁,其实也是被称为非独占锁或无阻塞锁。商品确实不会超卖,但是会看到大量的请求更新失败,所以还有可能有商品剩余。这里就引进可重入的概念。例如我们讲一个请求的有限周期设定在100ms或者50ms,如果再100ms内发生版本冲突导致不能更新的,则重新尝试请求,超过该时间再视为请求失败。

另一个方案,我们也可以限定可冲入的次数,即最多尝试几次请求后失败。

以上就是本人对高并发场景下设计的锁的理解,如有不全,多多指教。另外,我们实际在电商项目中,应该是用redis来处理高并发请求更完美。这点后面再细说。

Guess you like

Origin blog.csdn.net/LB_Captain/article/details/121174696