Xxl-Job实现订单30分钟未支付自动取消

话不多说,直接开干。

1.项目中引入依赖

<dependency>
     <groupId>com.xuxueli</groupId>
     <artifactId>xxl-job-core</artifactId>
     <version>2.3.0</version>
</dependency>

2.配置文件

xxljob:
  initenable: true
  # xxl-job, access token
  accessToken:
  cookie: XXL_JOB_LOGIN_IDENTITY=
  admin:
    addresses: http://192.168.57.***:8546/xxl-job-admin
  user:
    username: *****
    password: *****
  executor:
    address:
    ### xxl-job executor address
    appname: zhyyk-order-info
    ip:
    port: 8866
    ### xxl-job log retention days
    logretentiondays: 30
    ### xxl-job log path
    logpath: /Users/Documents/projectLogs

3.配置xxlJob任务参数

package com.xyhsoft.zhyyk.service.job;


import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;


@Component
@PropertySource("classpath:xxl-job-info.properties")
@ConfigurationProperties(prefix = "xxl-job-info")
@Data
@ToString
public class XxlJobInfoVO {
    private String jobGroup;
    private String jobDesc;
    private String author;
    private String scheduleType;
    private String glueType;
    private String executorHandler;
    private String executorRouteStrategy;
    private String misfireStrategy;
    private String executorBlockStrategy;
    private String executorTimeout;
    private String executorFailRetryCount;
    private String glueRemark;
    private String triggerStatus;

}
# \u5B9A\u65F6\u5668\u7EC4
xxl-job-info.jobGroup=1
# \u63CF\u8FF0
xxl-job-info.jobDesc=\u667A\u6167\u533B\u517B\u5EB7\u8BA2\u5355\u5B9A\u65F6\u53D6\u6D88\u652F\u4ED8
# \u521B\u5EFA\u8005
xxl-job-info.author=stx
# \u5B9A\u65F6\u5668\u7C7B\u578B
xxl-job-info.scheduleType=CRON
# glue\u7C7B\u578B
xxl-job-info.glueType=BEAN
# \u6267\u884C\u5668\u4EFB\u52A1handler
xxl-job-info.executorHandler=cancelPay
# \u6267\u884C\u5668\u8DEF\u7531\u7B56\u7565
xxl-job-info.executorRouteStrategy=FIRST
# \u8C03\u5EA6\u8FC7\u671F\u7B56\u7565
xxl-job-info.misfireStrategy=DO_NOTHING
# \u963B\u585E\u5904\u7406\u7B56\u7565
xxl-job-info.executorBlockStrategy=SERIAL_EXECUTION
# \u4EFB\u52A1\u6267\u884C\u8D85\u65F6\u65F6\u95F4\uFF0C\u5355\u4F4D\u79D2
xxl-job-info.executorTimeout=0
# \u5931\u8D25\u91CD\u8BD5\u6B21\u6570
xxl-job-info.executorFailRetryCount=0
# GLUE\u5907\u6CE8
xxl-job-info.glueRemark=GLUE\u4EE3\u7801\u521D\u59CB\u5316
# \u8C03\u5EA6\u72B6\u6001\uFF1A0-\u505C\u6B62\uFF0C1-\u8FD0\u884C
xxl-job-info.triggerStatus=1

4.创建定时任务

根据业务逻辑,上传订单id和自动取消的时间表达式。

Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MINUTE, 30);
String cron = DateUtils.getDateToCron(cal.getTime());
util.uploadCancelJob(cron, orderId);

 上传定时任务相关代码

package com.xyhsoft.zhyyk.service.job;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
public class CancelPayUtil {

    @Value("${xxljob.cookie}")
    private String cookie;

    @Value("${xxljob.admin.addresses}")
    private String xxlJobAdminAddr;

    @Autowired
    private XxlJobInfoVO xxlJobInfoVO;
  

    public Boolean uploadCancelJob(String cron,String orderId){
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        //设置为form方式
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Cookie", cookie);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        map.add("jobGroup", xxlJobInfoVO.getJobGroup());
        map.add("jobDesc", xxlJobInfoVO.getJobDesc());
        map.add("author", xxlJobInfoVO.getAuthor());
        map.add("alarmEmail", "");
        map.add("scheduleType", xxlJobInfoVO.getScheduleType());
        map.add("scheduleConf", cron);
        map.add("cronGen_display", cron);
        map.add("schedule_conf_CRON", cron);
        map.add("schedule_conf_FIX_RATE", "");
        map.add("schedule_conf_FIX_DELAY", "");
        map.add("glueType", xxlJobInfoVO.getGlueType());
        map.add("executorHandler", xxlJobInfoVO.getExecutorHandler());
        map.add("executorParam", orderId);
        map.add("executorRouteStrategy", xxlJobInfoVO.getExecutorRouteStrategy());
        map.add("childJobId", "");
        map.add("misfireStrategy", xxlJobInfoVO.getMisfireStrategy());
        map.add("executorBlockStrategy", xxlJobInfoVO.getExecutorBlockStrategy());
        map.add("executorTimeout", xxlJobInfoVO.getExecutorTimeout());
        map.add("executorFailRetryCount", xxlJobInfoVO.getExecutorFailRetryCount());
        map.add("glueRemark", xxlJobInfoVO.getGlueRemark());
        map.add("glueSource", "");
        map.add("triggerStatus",xxlJobInfoVO.getTriggerStatus());
        HttpEntity<MultiValueMap<String, String>> requestb = new HttpEntity<MultiValueMap<String, String>>(map, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(xxlJobAdminAddr+"/jobinfo/add", requestb, String.class);
        String regionString = response.getBody();//获取请求体
        JSONObject jsonObject = JSONObject.parseObject(regionString);//将请求体转化为json格式
        String code = jsonObject.getString("code");
        if (!("200".equals(code))) {
            throw new IllegalArgumentException("xxl-job定时任务创建失败");
        }
        return true;
    }

5.时间到期执行取消逻辑

订单状态枚举

package com.xyhsoft.zhyyk.service.job;


public enum OrderStatusEnum {

    //100=待支付
    TO_BE_PAID(100, "待支付"),

    //200=已过期
    EXPIRED(200, "已过期"),

    //300=已支付
    PAID(300, "已支付");

    //订单状态
    private final int status;

    //状态描述
    private final String description;

    OrderStatusEnum(int status, String description)
    {
        this.status = status;
        this.description = description;
    }
    public int getStatus()
    {
        return status;
    }

    public String getDescription()
    {
        return description;
    }
}

 订单取消逻辑

/**
 * @Descript: 订单超时处理类
 * @Author: stx
 * @Date: 12/23/22 2:34 PM
 */
@Slf4j
@Component
public class OrderTimeOutJob {


    @Autowired
    private OrderMapper orderMapper;


    @XxlJob("cancelPay")
    public void job() {
        log.info(">>>>>>>>进入xxl-job定时任务执行方法");
        String orderId = XxlJobHelper.getJobParam();
        orderMapper.update(entity:null Wrappers.< ~ > lambdaUpdate(
                .eg(Order::getId, orderId)
                //待支付状态
                .eq(Order::getStatus, OrderStatusEnum.TO_BE_PAID.getStatus())
                //设置成已过期
                .set(Order::getStatus,OrderStatusEnum.EXPIRED.getStatus())
                //设置过期时间
                set(Order::getExpireDateTime, LocalDateTime.now(Zoneoffset.ofHours(8)))
    }

}

到这就结束了,建议收藏,我只是建议哈。

总结:

使用xxljob实现超时订单自动取消可能存在以下一些缺点:

  1. 依赖于定时任务:xxljob是一个分布式任务调度平台,需要在服务器上部署并配置定时任务。这意味着你需要额外的服务器资源和维护工作来支持这个功能。如果你的应用程序规模较小或者没有专门的服务器管理团队,这可能会增加一些负担。

  2. 实时性较差:xxljob是基于定时任务的调度系统,它依赖于任务的调度周期。如果你设置的调度周期比较长,例如每隔5分钟检查一次订单超时情况,那么订单的取消可能会有一定的延迟。这对于某些业务场景可能不够实时。

  3. 扩展性限制:xxljob本身是一个任务调度平台,它的主要功能是执行定时任务。如果你的业务需求变得更加复杂,例如需要根据不同的条件来判断订单是否超时,或者需要与其他系统进行交互来取消订单,那么xxljob可能无法满足这些需求。你可能需要额外的定制开发来扩展xxljob的功能。

  4. 单点故障风险:如果你只有一个xxljob的实例在运行,那么它成为了单点故障的风险。如果这个实例出现故障或者停机,订单取消功能将无法正常工作。为了提高可用性,你需要考虑部署多个xxljob实例以及相应的负载均衡和故障转移机制。

  5. 任务调度管理复杂:xxljob虽然提供了任务调度的功能,但是它需要你自己管理任务的状态、调度规则和执行结果等。这可能需要你编写和维护一些额外的代码来管理这些任务。对于一些简单的任务来说,这可能没有太大的问题,但是如果任务规模较大或者复杂度较高,可能需要投入更多的开发和维护工作。

总之,尽管xxljob可以用来实现超时订单自动取消功能,但它也存在一些缺点和局限性。在选择使用xxljob之前,你需要仔细评估你的业务需求,并权衡xxljob的优点和缺点,以确定它是否适合你的应用场景。

<think>我们正在讨论如何实现延迟任务,特别是订单超时取消场景。根据引用内容,常见的实现方案有定时任务、延迟队列(如RabbitMQ的TTL+死信队列)、时间轮算法等。用户之前询问了RabbitMQ处理过期消息的机制,现在想了解更广泛的延迟任务实现方法。 我将从以下几个方面展开: 1. 定时任务轮询(最基础但存在性能瓶颈) 2. 延迟队列(RabbitMQ等消息队列的延迟特性) 3. 时间轮算法(高性能方案,如Netty的HashedWheelTimer) 4. Redis的有序集合(ZSET)和过期事件 5. 分布式任务调度框架(如Quartz, XXL-JOB) 注意:引用[1][2][3][4]都提到了订单超时取消场景,其中引用[1][2][4]重点提到了延迟队列,引用[3]提到了时间轮,引用[4]提到了定时任务的弊端。我们将综合这些方案进行说明。 下面开始详细回答: </think> 实现订单超时取消等延迟任务,主要有以下五种技术方案,各有适用场景和性能差异: --- ### 一、定时任务轮询(简单但低效) **原理**:周期性扫描数据库,检查超时订单 ```java @Scheduled(fixedRate = 60000) // 每分钟执行 public void cancelTimeoutOrders() { List<Order> orders = orderDao.findByStatusAndCreateTimeBefore( OrderStatus.UNPAID, LocalDateTime.now().minusMinutes(30) // 30分钟支付 ); orders.forEach(order -> orderService.cancel(order)); } ``` - **优点**:实现简单 - **缺点**: - 扫描延迟高(最大等于轮询间隔) - 数据库压力大(10万订单时扫描耗时约200ms[^3]) - 高频扫描影响写性能 - **适用场景**:小型系统(日订单量<1万) --- ### 二、延迟队列(RabbitMQ方案) **原理**:利用消息TTL+死信队列 ```java // 1. 发送延迟消息 rabbitTemplate.convertAndSend( "order.delay.exchange", "order.create", orderId, msg -> { msg.getMessageProperties().setExpiration("1800000"); // 30分钟TTL return msg; } ); // 2. 监听死信队列处理超时 @RabbitListener(queues = "order.cancel.queue") public void handleOrderCancel(String orderId) { orderService.cancel(orderId); } ``` - **优点**:解耦性好,吞吐量中等(约5k/秒[^3]) - **缺点**: - 消息堆积时过期检查不及时 - 消息级TTL需逐条设置 - **适用场景**:中型系统(日订单1万~50万) --- ### 三、时间轮算法(高性能方案) **原理**:环形队列+定时指针扫描(Netty HashedWheelTimer) ```java // 初始化时间轮(512槽位,100ms/槽) HashedWheelTimer timer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 512); // 提交延迟任务 timer.newTimeout(timeout -> { orderService.cancel(orderId); }, 30, TimeUnit.MINUTES); // 延迟30分钟 ``` - **优点**: - 性能极高(10万任务添加耗时<10ms[^3]) - 精度可调(毫秒级) - **缺点**: - 内存消耗大(所有任务驻留内存) - 宕机丢失任务 - **适用场景**:单机高性能场景(如游戏服务器) --- ### 四、Redis有序集合(分布式方案) **原理**:用ZSET存储订单超时时间戳 ```python # 添加订单(score=超时时间戳) redis.zadd("order:timeout", {order_id: time.time() + 1800}) # 定时扫描超时订单 while True: # 获取已超时的最小score订单 results = redis.zrangebyscore("order:timeout", 0, time.time(), start=0, num=1) if not results: time.sleep(0.1) # 无任务时短暂等待 continue order_id = results[0] redis.zrem("order:timeout", order_id) # 删除记录 cancel_order(order_id) # 执行取消 ``` - **优点**: - 分布式支持 - 吞吐量高(约8k/秒[^3]) - **缺点**:需自行实现扫描逻辑 - **优化**:多消费者分片处理 ```java // 分片键 = orderId.hashCode() % SHARD_COUNT String shardKey = "order:timeout:" + (orderId.hashCode() & 1023); ``` --- ### 五、分布式调度框架(企业级方案) **工具**:XXL-JOB、Quartz、SchedulerX **原理**:中心化调度+分片执行 ```java @XxlJob("cancelTimeoutOrders") public void cancelOrders() { // 获取分片参数 int shardIndex = XxlJobHelper.getShardIndex(); int shardTotal = XxlJobHelper.getShardTotal(); // 按分片查询订单 List<Order> orders = orderDao.findTimeoutOrders(shardIndex, shardTotal); orders.forEach(order -> cancel(order)); } ``` - **优点**: - 支持动态扩缩容 - 故障转移和重试机制 - **缺点**:架构复杂 - **适用场景**:大型分布式系统(日订单>100万) --- ### 方案对比(10万订单场景) | **方案** | 吞吐量 | 延迟精度 | 复杂度 | 数据丢失风险 | |-------------------|------------|----------|--------|--------------| | 定时任务轮询 | 500/秒 | 分钟级 | ★☆☆☆☆ | 低 | | RabbitMQ延迟队列 | 5,000/秒 | 秒级 | ★★★☆☆ | 中(消息堆积)| | 时间轮算法 | 100,000/秒 | 毫秒级 | ★★☆☆☆ | 高(宕机) | | Redis ZSET | 8,000/秒 | 秒级 | ★★★☆☆ | 低(持久化) | | 分布式调度框架 | 可水平扩展 | 秒级 | ★★★★☆ | 极低 | > 📌 **选型建议**: > - 小型系统:定时任务 > - 中大型系统:RabbitMQ/Redis > - 超高并发:时间轮+持久化备份 > - 企业级系统:分布式调度框架+Redis --- ### 容灾设计关键点 1. **消息持久化** RabbitMQ需开启队列和消息持久化 2. **消费幂等性** ```java if(ORDER_CANCELED.equals(order.getStatus())) return; // 状态检查 ``` 3. **补偿机制** 定时核对超时订单取消记录 4. **监控告警** 监控消息积压量和取消任务执行耗时 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值