电商系统中的掉单问题( 二 )


@XxlJob("syncPaymentResult")public ReturnT syncPaymentResult(int hour) {// 查询一段之间支付中的流水List pendingList = payMapper.getPending(now.minusHours(hour));for (PayDO payDO : pendingList) {// 主动去第三方查PaymentStatusResult paymentStatusResult = paymentService.getPaymentStatus(paymentId);// 第三方支付中if (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())) {continue;}// 支付完成, 获取到终态// 1.更新流水payMapper.updatePayDO(payDO);// 2.通知订单服务orderService.notifyOrder(notifyLocalRequestVO);}return ReturnT.SUCCESS;}
定时任务虽然简单,但是也存在以下问题 。
定时任务的频率设置不好把控,间隔短容易对数据库造成较大压力;间隔长则不实时,容易出现轮询不到支付成功状态的情况 。
事实上,在用户跳转钱包之后,通常会很快完成支付,如果在短时间内没有完成支付,那么一般也不会再进行支付了 。所以从发起支付开始,从第三方查询支付结果的频率应该是递减的 。
定时任务扫表,肯定会对数据库造成压力,如果数据量大,可能影响会更大 。
可以考虑单独创建一张支付中流水表,定时任务去扫描这张表,获取到支付最终态后,就删除对应的记录 。
延时消息查询
在发起支付后,发送一个延时消息,因为用户跳转到钱包后,通常会很快支付,所以可以以 10s、30s、1min、、2min、5min、7min…这种频率去查询支付订单的状态,这里可以用一个队列结构实现,队列里存放下一次查询的时间间隔 。
延时消息的方案相较于定时轮询的方案而言,时效性更好,而且无需扫表,对数据库造成的压力也较小 。
// 控制查询频率的队列, 时间单位为sDeque queue = new LinkedList<>();queue.offer(10);queue.offer(30);queue.offer(60);// 支付订单号PaymentConsultDTO paymentConsultDTO = new PaymentConsultDTO();paymentConsultDTO.setPaymentId(paymentId);paymentConsultDTO.setIntervalQueue(queue);// 发送延时消息Message message = new Message();message.setTopic("PAYMENT");message.setKey(paymentId);message.setTag("CONSULT");message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8));try {// 第一个延时消息, 延时10slong delayTime = System.currentTimeMillis() + 10 * 1000;// 设置消息需要被投递的时间message.setStartDeliverTime(delayTime);SendResult sendResult = producer.send(message);} catch (Throwable th) {log.error("[sendMessage] error: ", th);}// 在消费到延时消息后, 向第三方查询支付订单的状态, 如果还在支付中, 就继续发送下一个延时消息, 延时间隔从队列结构中取.// 如果获取到最终态, 就去更新支付订单状态, 并通知订单服务.@Component@Slf4jpublic class ConsultListener implements MessageListener {//消费者注册, 监听器注册@Overridepublic Action consume(Message message, ConsumeContext context) {// UTF-8解析String body = new String(message.getBody(), StandardCharsets.UTF_8);PaymentConsultDTO paymentConsultDTO = JsonUtil.parseObject(body, new TypeReference() {});if (paymentConsultDTO == null) {return Action.ReconsumeLater;}// 获取支付流水PayDO payDO = payMapper.selectById(paymentConsultDTO.getPaymentId());// 查询支付状态PaymentStatusResult paymentStatusResult = payService.getPaymentStatus(paymentStatusContext);// 如果还在支付中, 则继续投递下一个延时消息if (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())){// 发送延时消息Message msg = new Message();message.setTopic("PAYMENT");message.setKey(paymentConsultDTO.getPaymentId());message.setTag("CONSULT");// 下一个延时消息的频率Long delaySeconds = paymentConsultDTO.getIntervalQueue().poll();message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8));try {Long delayTime = System.currentTimeMillis() + delaySeconds * 1000;// 设置消息需要被投递的时间message.setStartDeliverTime(delayTime);SendResult sendResult = producer.send(message);} catch (Throwable th) {log.error("[sendMessage] error: ", th);}return Action.CommitMessage;}// 获取到最终态// 更新支付订单状态// 通知订单服务return Action.CommitMessage;}}