问题说明
最近做了一个功能,通过Rocket MQ 作为消息队列,根据条件生成奖励数据,在高并发的情况,一次性产生消息15万多条,实际数据只有其中的一半,导致生成的奖励数据重复。虽然在奖励生成的消费者端做了重复校验,但是还是没有避免。
数据库的事务隔离级别为:Read Committed
/**
* 根据订单的生产奖励信息
*
* @param order 订单议信息
*/
@Override
@Transactional
public void buildAgreementAwardDataByOrder(Order order) {
}
判断唯一性的示例代码,在数据库中查询消费者是否已经存在有效的奖励数据信息,如果不存在就生成奖励数据,如果存在就直接跳过:
/**
* 判断当前用户是否已经存在有效的奖励信息
*
* @param order 协议信息
* @param awardStageEnum
* @return
*/
private Boolean existValidAwardDetail(Order order, AwardStageEnum awardStageEnum, TaskTypeEnum taskTypeEnum) {
List<OrderAwardDetail> list = this.awardDetailRepository.lambdaQuery().eq(OrderAwardDetail::getActivityCode, order.getActivityCode())
.eq(OrderAwardDetail::getCustomerCode, order.getCustomerCode())
.eq(OrderAwardDetail::getAwardStage, awardStageEnum.getValue())
.eq(OrderAwardDetail::getTaskType, taskTypeEnum.getDictCode())
.ne(OrderAwardDetail::getCheckStatus, AwardCheckStatusEnum.ABANDON.getDictCode()).list();
if (CollectionUtils.isNotEmpty(list)) {
log.error("当前用户已经存在本周期内有效的奖励信息,用户编码为:【" + order.getCustomerCode() + ",活动编码【" + order.getActivityCode() + "】");
return true;
}
return false;
}
当在高并发的情况,同样订单order进入生成奖励数据逻辑,判断奖励是否存在奖励可能失效,因为在@Transactional未设置实物隔离级别,将采用数据库的事务隔离级别,而数据库的隔离级别是Read Committed,在未提交之前,相同的order进入后都判断到未创建奖励数据,导入生成重复的奖励信息。
解决方案
- 在消费者获取消息后,引入redis做过期缓存,将订单的id作为唯一key,缓存时间为设置为5分钟,如果存在直接返回,不存在则写入缓存,往下执行生成奖励信息;
- 在奖励表中创建一个基于订单信息的唯一索引,如果Redis 判断因为某些原因失效,最后数据库唯一索引也能避免生成重复的奖励信息。