作者:京东零售 邢成
引言
多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。
银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。
从事零售供应链库存业务,对库存数量操作增减十分频繁,同样存在类似上述银行取款遇到的问题,库存数量操作有误势必给前台销售产生损失影响,因此需要关注对库存数量并发操作下的一致性。
下面通过一个真实的案例分享在并发情况下如何保证库存数量的准确性。
问题是什么-加锁失效
看看下面这段流程和代码,思考会有并发问题吗?
1. 加锁前 ,获取箱子明细数据,此处在锁之外,存在并发脏读问题
2. 加锁后 ,并进行箱子上架分批次回传业务处理
3. 加锁后, 更新箱子明细上架数量逻辑:已上架数量 = 加锁前的明细数据(脏读) + 报文回传的明细数据 直接进行行更新
原因是什么-加锁的位置不正确
核心的问题原因
1.业务分布式锁失效: 使用分布式锁加锁了,但是仍然使用加锁前查询的数据,导致出现脏读
2.Mysql锁失效: 数据库更新时,未上任何锁,导致脏读的数据直接覆盖更新当前行
有同学这时问了,为啥防重码也没有生效呢?
防重码主要是用作幂等逻辑的,同一个请求多次处理,结果仍然是相同的。
但是这是两次不同的请求,防重码是不同的,因此不能只依赖防重码保证一致性。
解决方案有哪些
1、代码层面: 使用锁(如互斥锁、读写锁、分布式锁等)来控制资源的访问,数据获取的全部操作都需要再获取锁后才进行。
将获取箱子明细的代码移动到加锁之后,只有获取到分布式锁,才能执行分批次上架查询和更新(串行化)
对应改造后的代码:
2、数据库层面: 实现事务管理,确保数据的一致性;合理设置事务隔离级别,以防止脏读、或者采用乐观锁或悲观锁来处理并发更新,合理设计查询效率,减少锁竞争。
数据库的并发上锁处理和业务代码的上锁是互补的关系
因为无法保证后续业务的调整或其他业务代码的调用能始终保持获取数据的一致性,数据库的并发上锁处理更多是一种兜底保证机制。
乐观锁更新
悲观锁更新
扩展方案
1.应用程序设计: 在应用程序设计阶段,尽量避免长时间持有数据库连接或事务,减少并发操作的可能性,利用AI代码评审或者人工提前找出可能出现并发问题的地方;合理设置锁的粒度,避免锁失效。
- 网络负载层面: 采用限流控制访问频率;采用分布式数据库,进行数据分片,降低单节点并发压力;使用负载均衡,将网络请求分发到不同的服务器,提高系统处理并发的能力,防止系统过载。
1.请求层面: 前端点击防重、系统幂等防重、尽可能降低同一请求的多次重试访问引起的一致性问题。
通过以上措施,可以在不同层面有效地防止并发问题,保证系统的数据的一致性