Java 面试官:订单下完人跑咧?超时不付款咋办,给他干掉不香嘛!
兄弟们,咱这几年面试走南闯北的,碰见个问题问得是真多,但说实话,这玩意儿就像火锅蘸料,不整一份辣点儿的都对不起这份代码。
今天咱就唠唠——下单不付款,这人跑了,订单咋整?扔那儿臭着?不可能!得想法给他干掉——当然,是那订单哈,不是人。
这事儿为啥老有人问?真就因为容易出事
你想啊——
- 用户点完付款,结果微信那边卡了,他一关页面,这订单就悬着咧;
- 后台你不整点东西盯着,他 3 天后付款了,你还真得发货?开玩笑呢;
- 30分钟不付钱的单子,全给我干掉,这逻辑谁来盯,怎么盯,盯完谁动手取消,咋防止误伤,这些都得说清;
面试官问这玩意儿,说白了就是看你脑袋里有没有货,能不能把复杂事整清,别上来一句“定时任务搞定”,真就当面写答案纸上了兄弟。
几种方案咱细嚼一口口整
1. 定时任务轮询,祖传方式,粗暴顶用但上不了厅堂
@Scheduled(cron = "0 */1 * * * ?")
public void cancelTimeoutOrders() {
List<Order> orders = orderMapper.findUnpaidBefore(timeNowMinus30Min());
for (Order o : orders) {
orderService.cancel(o.getId());
}
}
说实话,能跑但不太体面;数据库跟打仗似的每分钟扫一遍,全靠硬杠。
缺点贼多:高并发时候直接嘎了;你多实例服务还得加分布式锁,不然就像多个人同时捞锅底,互相抢锅铲,场面太乱了——最后锅也打了,汤也撒了。
2. ScheduledExecutorService,小场面够用,真上生产?你敢我都怕
scheduler.schedule(() -> cancelOrder(orderId), 30, TimeUnit.MINUTES);
单体项目里还行,起个定时器就完事儿,贼快。但你只要一重启,那任务就像老板答应的加薪——消失得连个毛都不剩。
而且你这服务是分布式架构的话,谁负责调度?没商量就上?别人节点也调一遍?人家都开始付款了你还在 cancel……
3. RabbitMQ 死信队列,大厂标配,抗造还骚气
别说,咱真在几个项目里都搞过这玩意儿,稳定得一批。
创建订单那会儿,顺手塞个延迟消息进去 TTL 30 分钟,到点自动扔到死信队列里去,然后由消费者监听拿出来干掉。
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "order.dlx.exchange");
args.put("x-dead-letter-routing-key", "order.dlx.routingkey");
args.put("x-message-ttl", 1800000); // 30分钟
Queue delayQueue = new Queue("order.delay.queue", true, false, false, args);
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderId);
@RabbitListener(queues = "order.dlx.queue")
public void cancelOrder(String orderId) {
if (!orderService.isPaid(orderId)) {
orderService.cancel(orderId);
}
}
优雅、解耦、还省心。只要 MQ 别挂——挂了你就得祈祷兜底逻辑不掉链子。
4. Redis ZSet,轻量级骚操作,吞吐性能拉满
zadd order:timeout 1715067900 order123
Set<String> ids = redisTemplate.opsForZSet().rangeByScore("order:timeout", 0, now);
for (String id : ids) {
cancel(id);
redisTemplate.opsForZSet().remove("order:timeout", id);
}
这个好用是真好用,简单粗暴、能跑贼快——但你要是没持久化,服务一挂,数据没了就全白整。
建议上 Redisson 或者配合持久化搞一搞,要不宕个机你就掉链子了。
幂等性 + 兜底,两个不整你就是在赌命
你 MQ 消息可能丢,你 Redis 线程可能死,你定时任务也可能没触发;这时候怎么办?加兜底定时轮询扫一遍,再救一手。
还有那幂等性,必须整——用户正付款呢,你提前把订单干掉了,人家找你报销你赔不赔?
public void cancel(String orderId) {
Order o = orderService.getById(orderId);
if (o.status == WAIT_PAY) {
orderService.updateStatus(orderId, CANCELLED);
}
}
面试官要你说,你该咋装?
兄弟你得这么说:
“我们用 RabbitMQ 延迟消息 + 死信队列处理订单取消逻辑,同时 Redis ZSet 做延迟队列兜底保障;每次消费前都会检查订单状态做幂等处理,避免重复取消或误伤,另外我们还做了定时轮询兜底处理 MQ 异常场景。”
面试官一听:好家伙,这人能干活儿。
最后叨叨两句
这东西乍一看简单,真整起来坑贼多。光靠“定时任务”你能唬谁?RabbitMQ、Redis、幂等处理、兜底任务全都安排上你才是懂业务的。
别怕麻烦,怕的是你线上翻车,订单乱了,客服天天找你开会,你小命就交代咧。
实话实说,系统可靠性这玩意就看这细节活儿,要么不干,要干就干成个稳的。