场景
设计一个预售下单场景
springboot +ReactiveMongoRepository
一、高并发问题
在高并发下,webflux 表现的性能异常强劲,编写第一个测试接口,测出的数据全是脏读脏写
return userService.getUserWithAuthorities()
.flatMap(user -> payinfoService.findOne(user.getId())
.flatMap(p -> goodsService.findOne(goodsDTO.getId()).map(g -> {
//检查库存
if (g.getNum() >= goodsDTO.getNum()) {
//库存正常,减扣库存
g.setNum(g.getNum() - goodsDTO.getNum());
解决方案
a.事务
添加事务,使用事务的ACID 特性处理,效果不理想,拖慢了性能
b.mongo 写锁
依赖mongo 的写锁去控制库存余量,砍掉检查库存读库过程,脏读问题消失
Query query = new Query();
query.addCriteria(where("id").is(e.getId()));
Update update = new Update();
update.inc("num", -e.getNum());
return reactiveMongoTemplate.updateMulti(query, update, Goods.class);
二、超卖问题
当解决了脏读脏写,新的问题也出现了,超卖,几乎所有高并发都会面临的问题
解决方案
a.事务解决
结果如上,能解决问题,但性能和下单成功率被完全压住了
b. reactiveMongoTemplate getMatchedCount() 方法
利用mongo 的锁特性,加上自己编写的业务判断,将执行结果作为后续业务决断,代码如下,执行的时候
Query query = new Query();
query.addCriteria(where("id").is(e.getId()));
query.addCriteria(where("num").gt(0));//执行sql加上判断库存
Update update = new Update();
update.inc("num", -e.getNum());
return reactiveMongoTemplate.updateMulti(query, update, Goods.class);
return goodsService.subtractionGoods(goodsDTO).flatMap(gs -> {
OrderInfoDTO orderInfoDTO = new OrderInfoDTO();
//库存及金额没问题就下单
...
//解除事务读写冲突的性能限制,放开并发流量,失败就创建失败订单,
if (gs.getMatchedCount() > 0 && gs.getModifiedCount() > 0) {
orderInfoDTO.setStatus(OrderStatusEnum.STATUS_C.getCode());
} else {
orderInfoDTO.setStatus(OrderStatusEnum.PARAM_ERROR.getCode());
}
return orderInfoRepository.save(orderInfoMapper.toEntity(orderInfoDTO)).map(orderInfoMapper::toDto);
});
小插曲
mongodb 金额问题 NumberDecimal 和java BigDecimal 使用的时候 需要做一个转换才能避免一些异常,使用 new Decimal128(value) 方法