- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在电商、外卖、票务等系统中,订单超时未支付自动取消是一个常见的需求.
这个功能乍一看很简单,甚至很多初学者会觉得:"不就是加个定时器么?" 但真到了实际工作中,细节的复杂程度往往会超乎预期.
这里我们从基础到高级,逐步分析各种实现方案,最后分享一些在生产中常见的优化技巧,希望对你会有所帮助.
适用场景:订单数量较少,系统并发量不高.
延时队列是Java并发包(java.util.concurrent)中的一个数据结构,专门用于处理延时任务.
订单在创建时,将其放入延时队列,并设置超时时间.
延时时间到了以后,队列会触发消费逻辑,执行取消操作.
示例代码:
import java.util.concurrent.*;
public class OrderCancelService {
private static final DelayQueue<OrderTask> delayQueue = new DelayQueue<>();
public static void main(String[] args) throws InterruptedException {
// 启动消费者线程
new Thread(() -> {
while (true) {
try {
OrderTask task = delayQueue.take(); // 获取到期任务
System.out.println("取消订单:" + task.getOrderId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
// 模拟订单创建
for (int i = 1; i <= 5; i++) {
delayQueue.put(new OrderTask(i, System.currentTimeMillis() + 5000)); // 5秒后取消
System.out.println("订单" + i + "已创建");
}
}
static class OrderTask implements Delayed {
private final long expireTime;
private final int orderId;
public OrderTask(int orderId, long expireTime) {
this.orderId = orderId;
this.expireTime = expireTime;
}
public int getOrderId() {
return orderId;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.expireTime, ((OrderTask) o).expireTime);
}
}
}
优点:
缺点:
适用场景:订单数量较多,但系统对实时性要求不高.
轮询是最容易想到的方案:定期扫描数据库,将超时的订单状态更新为“已取消”.
示例代码:
public void cancelExpiredOrders() {
String sql = "UPDATE orders SET status = 'CANCELLED' WHERE status = 'PENDING' AND create_time < ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis() - 30 * 60 * 1000)); // 30分钟未支付取消
int affectedRows = ps.executeUpdate();
System.out.println("取消订单数量:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
优点:
缺点:
优化建议:
适用场景:适合对实时性有要求的中小型项目.
Redis 的 List 或 Sorted Set 数据结构非常适合用作延时任务队列.
我们可以把订单的超时时间作为 Score,订单 ID 作为 Value 存到 Redis 的 ZSet 中,定时去取出到期的订单进行取消.
例子:
public void addOrderToQueue(String orderId, long expireTime) {
jedis.zadd("order_delay_queue", expireTime, orderId);
}
public void processExpiredOrders() {
long now = System.currentTimeMillis();
Set<String> expiredOrders = jedis.zrangeByScore("order_delay_queue", 0, now);
for (String orderId : expiredOrders) {
System.out.println("取消订单:" + orderId);
jedis.zrem("order_delay_queue", orderId); // 删除已处理的订单
}
}
优点:
缺点:
适用场景:对超时事件实时性要求高,并且希望依赖 Redis 本身的特性实现简单的任务调度.
Redis 提供了 Key 的过期功能,结合 keyevent 事件通知机制,可以实现订单的自动取消逻辑.
当订单设置超时时间后,Redis 会在 Key 过期时发送通知,我们只需要订阅这个事件并进行相应的处理.
例子:
public void setOrderWithExpiration(String orderId, long expireSeconds) {
jedis.setex("order:" + orderId, expireSeconds, "PENDING");
}
public void subscribeToExpirationEvents() {
Jedis jedis = new Jedis("localhost");
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel, String message) {
if (channel.equals("__keyevent@0__:expired")) {
System.out.println("接收到过期事件,取消订单:" + message);
// 执行取消订单的业务逻辑
}
}
}, "__keyevent@0__:expired"); // 订阅过期事件
}
优点:
缺点:
notify-keyspace-events
配置。注意事项: 要使用 Key 过期事件,需要确保 Redis 配置文件中 notify-keyspace-events 的值包含 Ex。比如:
notify-keyspace-events Ex
适用场景:高并发系统,实时性要求高.
订单创建时,将订单消息发送到延迟队列(如RabbitMQ 的 x-delayed-message 插件).
延迟时间到了以后,消息会重新投递到消费者,消费者执行取消操作.
示例代码(以RabbitMQ为例):
public void sendOrderToDelayQueue(String orderId, long delay) {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
ConnectionFactory factory = new ConnectionFactory();
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.exchangeDeclare("delayed_exchange", "x-delayed-message", true, false, args);
channel.queueDeclare("delay_queue", true, false, false, null);
channel.queueBind("delay_queue", "delayed_exchange", "order.cancel");
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.headers(Map.of("x-delay", delay)) // 延迟时间
.build();
channel.basicPublish("delayed_exchange", "order.cancel", props, orderId.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
优点:
缺点:
适用场景:订单取消操作复杂,需要分布式支持.
定时任务框架,比如:Quartz、Elastic-Job,能够高效地管理任务调度,适合处理批量任务.
比如 Quartz 可以通过配置 Cron 表达式,定时执行订单取消逻辑.
示例代码:
@Scheduled(cron = "0 */5 * * * ?")
public void scanAndCancelOrders() {
System.out.println("开始扫描并取消过期订单");
// 这里调用数据库更新逻辑
}
优点:
缺点:
我的项目实战和工作经验分享 。
适用场景:需要处理实时性较高的订单取消,同时结合复杂业务逻辑,例如根据用户行为动态调整超时时间.
可以借助事件流处理框架(如 Apache Flink 或 Spark Streaming),实时地处理订单状态,并触发超时事件.
每个订单生成后,可以作为事件流的一部分,订单未支付时通过流计算触发超时取消逻辑.
示例代码(以 Apache Flink 为例):
DataStream<OrderEvent> orderStream = env.fromCollection(orderEvents);
orderStream
.keyBy(OrderEvent::getOrderId)
.process(new KeyedProcessFunction<String, OrderEvent, Void>() {
@Override
public void processElement(OrderEvent event, Context ctx, Collector<Void> out) throws Exception {
// 注册一个定时器
ctx.timerService().registerProcessingTimeTimer(event.getTimestamp() + 30000); // 30秒超时
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Void> out) throws Exception {
// 定时器触发,执行订单取消逻辑
System.out.println("订单超时取消,订单ID:" + ctx.getCurrentKey());
}
});
优点:
缺点:
每种方案都有自己的适用场景,大家在选择的时候,记得结合业务需求、订单量、并发量来综合考虑.
如果你的项目规模较小,可以直接用延时队列或 Redis;而在大型高并发系统中,消息队列和事件流处理往往是首选.
当然,代码实现只是第一步,更重要的是在实际部署和运行中进行性能调优,保证系统的稳定性.
希望这篇文章能给大家一些启发,也欢迎讨论其他可能的实现思路! 。
最后说一句(求关注,别白嫖我) 如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力.
求一键三连:点赞、转发、在看.
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer.
最后此篇关于订单超时自动取消,我们是这样做的。。。的文章就讲到这里了,如果你想了解更多关于订单超时自动取消,我们是这样做的。。。的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我尝试编写一个有多个链接表的解决方案。现在我有另一个问题: 我想将返回的行数限制为 1000。但我想显示 ID 1-1000,下一页 1001-2000。ID可以以不规则的顺序存储在数据库中(ID 1
我已经尝试申请 Drupal 商业优惠券大约 2 天了。我已经负责验证优惠券,但目前在尝试兑换优惠券时遇到了困难。 所以在我的回调函数中,我正在调用: my_module_coupons_coupon
[问]请帮忙,比如有一个数据 tbl_user | Id | name | | 1 | Bayu | | 2 | Indra | | 3 | Rangga | tbl_data | Id | user
我在 Android 应用程序中使用的一些 Parcelable 自定义类遇到了问题,我设法以一种非常奇怪的方式解决了这个问题。 仅在少数特定情况下,我在读取 parcelable 时发生了崩溃(这让
我一直在做一个项目,我需要在数据库中存储订单列表(在本例中为食品)。 我曾尝试四处寻找存储此类列表的最佳方式,但找不到任何方法。 目前,我将数据存储在 phpMyAdmin/SQL 中,订单存储为要打
目录 1、背景简介 2、订单业务 1、订单体系 2、流程管理 2
HBase案例:客户/订单 假设HBase 用于存储客户和订单信息。有两种核心记录类型被摄取:客户记录类型和订单记录类型。 客户记录类型将包含您通常期望的所有内容: 客户编号 客户名称
C-x C-b 显示缓冲区列表。首先是自然顺序,最近使用的缓冲区在顶部,隐藏的缓冲区在底部。 在那里,我现在可以按名称、大小、模式和文件对缓冲区进行排序。但是一旦我点击了这样的选项,我就无法回到原来的
我为 Woocommerce 添加了一个新的排序选项,它将按最低价格排序。我所有的价格都存储在一个自定义属性中,连同一些其他序列化数据。我想要的是有一个回调函数来反序列化这些数据,检查最低价格并按该价
想象一下我有一张 table : ID field1 field2 --- ------- ------ 111 1 11113 112
Kotlin forEach 是按数组的实际顺序遍历数组还是有时可能按其他顺序遍历数组?我的意思是这是否总是打印 1,2,3,...9 或者它可能会打印类似 1,5,3,4,... val numbe
我在 woocommerce 3+ 上创建了 html 电子邮件模板,但我无法通过订单 ID 获取订单项。我试过这个,但对我不起作用。 get_items(); foreach
我对将我自己的内部广告与 AdMob 的广告一起展示并使用按重要性顺序设置 eCPM 值的问题感到有些困惑。 我目前只与 AdMobs 的网络一起转换一个自家广告。 从常见问题解答和 AdMob 帮助
我正在尝试构建一个电子商务数据库,但我不了解订单,产品和客户之间的关系。 有很多数据库示例,但是它们太复杂了。是否有关于可能的表和关系的更简单的解释或示例。 谢谢。 最佳答案 如果客户可以拥有多个订单
我必须对电子商务系统进行一些更改以添加一些附加信息,并希望借此机会进行一些改进并使其更加灵活。当客户下订单时,我们必须为每个订购的商品存储几项信息;例如,产品价格、运费、征收的税款、所做的任何调整。
我正在尝试新的 ASP.NET bundle 功能,但似乎无法让我的自定义排序正常工作。这是我的 JS 文件: bootstrap.js bootstrap.min.js jquery-1.7.2.i
我正在尝试以下代码,并希望获取日期之间的所有订单并打印它们 $orders = $my_query->posts; $order = wc_get_order( $order_id ); $or
我有 ORMLite 数据库对象,它有一个字段: @ForeignCollectionField(eager = true) public ForeignCollection blocks; 现在,当
除了调用 event_list_attendees 并寻呼与会者以尝试找到正确的用户匹配之外,是否有其他方法可以获取门票/订单的用户条形码 ID?这种方法会增加 eventbrite 服务器的负担,并
这个问题已经有答案了: What is a NullPointerException, and how do I fix it? (12 个回答) 已关闭 5 年前。 我制作了订单食品应用程序。当我单
我是一名优秀的程序员,十分优秀!