针对交易系统大促场景下热点数据写优化的相关案例。当然,不同的企业有不同的解决方案和实现,但是万变不离其宗,还是那句话,对于大型网站而言,其架构一定是简单和清晰的,而不是炫技般的复杂化,毕竟解决问题采用最直接的方式直击要害才是最见效的,否则事情只会变得越来越糟。
在大部分情况下,商品库存都是直接在关系型数据库中进行扣减,那么在限时抢购活动正式开始后,那些单价比平时更给力、更具吸引力的热卖商品大家肯定都会积极踊跃的参与抢购,这必然会产生大量针对数据库同一行记录的并发更新操作。因此数据库为了保证原子性,InnoDB引擎缺省会对同一行数据记录进行加锁,把前端的并发请求变成串行操作来确保数据更新时的一致性。
一、在RDBMS中扣减商品库存
先来看看如果是直接在数据库中扣减库存,应该如何避免商品超卖呢?在生产环境中我们可以通过乐观锁机制来避免这个问题,所谓乐观锁,简单来说,就是在item表中建立一个version字段。假设某一个热卖商品的实际库存为n,处于性能考虑对于查询库存操作是不建议加for update的,那么在并发场景下,必然会导致多个用户拿到的stock和version都一样。因此当第1个用户成功扣减商品库存后,则需要将item表中的version加1,这样一来,当第2个用户扣减库存时,由于version不匹配,那么为了提升库存扣减的成功率,可以适当进行重试,如果库存不足,则说明商品已经售罄,反之扣减库存后version继续加1。关于在数据库中使用乐观锁扣减库存的伪代码,如下所示:
-
public void testStock(int num) {
-
if (version不一致时的重试次数阈值) {
-
SELECT stock,version FROM item WHERE item_id=1;
-
if (如果查询的指定商品存在) {
-
if (判断stock是否够扣减) {
-
UPDATE item SET version=version+1,stock=stock-1 WHERE
-
item_id=1 AND version="+ version +";
-
if (扣减库存失败) {
-
/* version不一致时开始尝试重试 */
-
testStock(--num);
-
} else {
-
logger.info("扣减库存成功");
-
}
-
} else {
-
logger.warn("指定商品已售罄");
-
}
-
}
-
}
-
}
如果系统前端不配合做限流消峰等处理,随意放任大量的并发更新请求直接在数据库中扣减同一热卖商品的库存数据,这将会导致线程之间相互竞争InnoDB的行锁,由于数据库中针对同一行数据的更新操作是串行执行的,那么某一个线程在未释放锁之前,其余的线程将会全部阻塞在队列中等待拿锁,并发越高时,等待的线程也就会越多,这会严重影响数据库的TPS,从而导致RT线性上升,最终可能引发系统出现雪崩。