在业务复杂、数据量大、并发量大的业务场景中,典型的互联网架构一般分为几层:通常组装成HTML或者json返回的web-server层service层一般是service层数据层提供RPC调用接口,db提供固化数据存储。对于存货业务,一般有存货服务,提供存货查询、扣减、设置等RPC。接口:库存查询,stock-service本质上执行selectnumfromstockwheresid=$sid库存扣除,stock-service本质上执行updatestocksetnumnum=num-$reducewheresid=$sid库存设置,stock-service本质上执行updatestocksetnum=$num_newwheresid=$sid下单前,用户一般会查询库存,只有库存足够才允许扣除:如上图所示,通过查询界面,库存为5。当用户下单时,库存将被扣:如上图,采购3件商品时,通过扣款接口,最终库存为2。希望设计中常有容错机制,比如“重试”。如果通过扣款接口修改库存,在重试时可能会获取到错误的数据,导致重复扣款:如上图所示,如果在数据库层面存在重试容错机制,会导致扣款执行两次并以负错误库存结束。重试导致错误的根本原因是“推演”操作是非幂等操作,不能重复。如果改为setting操作,就不会出现这个问题:如上图,同样是购买3个单位的商品,通过settinginventory操作,即使有重试容错机制,也会不会弄错库存。设置库存是一个幂等操作。在并发量大的情况下,还会出现其他问题:如上图,两个并发操作,查询库存,都得到5个库存。接下来,用户有一个并发的购买动作(秒杀业务特别容易出现):如上图所示:用户1购买了3只股票,所以股票应该设置为2用户2购买了2只股票,所以股票应该设置为3这两个设置库存的接口是并发执行的,库存会先变成2再变成3,导致数据不一致(实际卖了5件,但是库存只扣了2,上一次库存isset会覆盖覆盖之前的并发操作)根本原因是设置操作发生时,库存和查询到的库存没有变化。理论上:当库存为5时,用户1的库存设置可以成功设置当库存为5时,用户2的库存设置只有在实际执行时才能成功执行:库存为5,用户1的设置库存2应该确实成功将stock改成了2,而user2的setstock3应该会失败。升级修改很简单,股票设置界面,在stock-service上执行:updatestocksetnum=$ywheresid=$sid升级为:updatestocksetnum=$num_newwheresid=$sidandnum=$num_old这就是我们常说的“比较并设置”(CAS),这是一种常见的减少读写锁冲突,保证数据一致性的方法。综上所述,在业务复杂、数据量大、并发量大的情况下,库存扣减容易造成数据不一致。常见的优化方案有两种:实现时调用“设置库存”接口,保证数据的幂等性设置“库存”接口时,需要添加原始库存的对比,才能设置成功,可解决高并发下库存扣减的一致性问题【本文为专栏作者“58神剑”原创稿件,转载请联系原作者】点此阅读更多该作者好文章
