java定时任务处理数据技巧

场景1

有一个定时任务,需要每次去处理订单表t_order status=0 未处理的数据,处理完成之后将状态status修改为成功1 处理失败后设置为失败2

简单做法

每次按照创建时间顺序,查询100条订单表t_order status=0 未处理的数据,查询到后处理成功的设置为成功 失败的设置为失败

容易出现的问题

1.当订单太多,例如一小时出现了100w条订单,此时容易出现堆积的情况

2.未生成定时任务日志,无法通过日志看出 定时任务的处理速度

3.失败的订单没有重试,当有一段时间由于网络原因所有订单都失败时,这些订单没办法重试

4.此次订单是可以修改订单状态 通过状态去判断是否成功的,如果你访问的是第三方数据库,无法去修改第三方数据库的状态,这种方式则不可取

新方案

假设我们查询的订单t_order是第三方数据库中的,需要通过定时任务去处理这些订单,处理完成后将订单状态修改为成功1 处理失败后设置为失败2

1.新建一张日志表,

CREATE TABLE t_task_log (

id bigint NOT NULL AUTO_INCREMENT COMMENT ‘主键id’,

query_start_time varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘定时任务查询条件开始时间’,

query_end_time varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘定时任务查询条件结束时间’,

task_start_time varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT ‘定时任务开始时间’,

task_end_time varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT ‘定时任务结束时间’,

total_num bigint DEFAULT NULL COMMENT ‘此次定时任务总操作条数’,

success_num bigint DEFAULT NULL COMMENT ‘此次定时任务总成功条数’,

fail_num bigint DEFAULT NULL COMMENT ‘此次定时任务总失败条数’,

cur_time_task_max_id varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘当前查询时间段 处理数据中的最大任务处理的最大id’,

status tinyint DEFAULT NULL COMMENT ‘状态 0新建 1完成 2 未完成’,

type int NOT NULL COMMENT ‘状态(1: 正常日志, 2: 重试日志)’,

repeat_ids varchar(10000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘重复的ids’,

create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,

last_modify_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最后更新时间’,

PRIMARY KEY (id),

KEY idx_last_modify_time (last_modify_time) USING BTREE,

KEY idx_query_start_time (query_start_time) USING BTREE,

KEY idx_query_end_time (query_end_time) USING BTREE,

KEY idx_task_start_time (task_start_time) USING BTREE,

KEY idx_task_end_time (task_end_time) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=1871762861052760066 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT=‘定时任务日志表’;

一张订单定时任务数据表

CREATE TABLE t_task_data (

id bigint NOT NULL AUTO_INCREMENT COMMENT ‘主键id’,

data_id varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT ‘查询的订单id’,

status tinyint DEFAULT NULL COMMENT ‘状态(0新建 3推送失败 4成功)’,

push_count tinyint DEFAULT NULL COMMENT ‘推送的次数’,

create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,

last_modify_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最后更新时间’,

PRIMARY KEY (id),

UNIQUE KEY uniq_data_id (data_id) USING BTREE,

KEY idx_last_modify_time (last_modify_time) USING BTREE,

KEY idx_status (status) USING BTREE,

KEY idx_status_push_doms_count_create_time (status,push_doms_count,create_time) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=2198 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT=‘推送数据记录表’;

处理流程

@TaskLock(lockTime = 3600)
public void execTask() throws ParseException {

log.info("task定时任务开始执行!");
//获取初始开始时间
String queryStartTime = config.getQueryStartTime();
Integer limitNum = config.getLimitNum();
Integer lessHours = config.getLessHours();
String maxId = null;
    if(limitNum>100){
throw new BusinessException("每次推送数量不能超过100");
}


String startTime = queryStartTime ;
String endTime = DateUtil.formatDate(DateUtil.addHours(new Date(), -lessHours), "yyyy-MM-dd HH:mm:ss") ;

//组装定时任务log
TaskLog newTaskLog = new TaskLog();
newTaskLog.setTaskStartTime(DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
newTaskLog.setQueryEndTime(endTime);
newTaskLog.setStatus(STATUS_NEW);

//查找最新一条日志
TaskLog taskLog = taskLogMapper.getLogByLastTime();
    if(taskLog != null){
//如果不是成功的,则继续执行上一次的任务
if(taskLog.getStatus().intValue() != STATUS_SUCCESS){
startTime = taskLog.getQueryStartTime();
endTime = taskLog.getQueryEndTime();
maxId = taskLog.getCurTimeTaskMaxId();
newTaskLog = taskLog;
log.info("Task 任务 有未完成任务 {}",JSON.toJSONString(newTaskLog));
}else{
startTime = taskLog.getQueryEndTime();
}
}

// 如果是新的
if(newTaskLog != taskLog){
Long totalNumByTimeRange = orderMapper.getTotalNumByTimeRange(startTime, endTime);
        if(totalNumByTimeRange==0){
log.info("task未找到需要处理的数据 定时任务结束startTime=" + startTime + " endTime:" + endTime);
            return;
}
newTaskLog.setTotalNum(totalNumByTimeRange);
newTaskLog.setSuccessNum(0L);
newTaskLog.setFailNum(0L);
newTaskLog.setQueryStartTime(startTime);
log.info("task 任务 插入新任务,{}" ,JSON.toJSONString(newTaskLog ));

newTaskLog.setTaskEndTime(DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
// 插入数据
taskLogMapper.insert(newTaskLog);
}
log.info("task 任务开始: startTime=" + startTime + " endTime:" + endTime  );


while(true){
//1.查询需要处理的数据
List<Order> orderList = orderMapper.queryByTimeRange(startTime, endTime,
maxId, limitNum);

        if( orderList.size()  == 0){
break;
}
Order dto = orderList.get(orderList.size() - 1);
String maxIdTemp = dto.getId();

        if(!StringUtils.isEmpty(maxId) && maxIdTemp.compareTo(maxId)<=0){
throw new RuntimeException("maxId 数据异常,请检查是否按照 asc 排序");
}

maxId = maxIdTemp;

//2.做一下幂等,过滤已经处理过的数据
List<Order> effectiveOrderList = filterEffectiveListBuildUpc(pushNikeDomsTaskLog,orderList);

        if(!CollectionUtils.isEmpty(effectiveOrderList)){
//将得到的订单进行处理
}
//4.将本次要处理的数据进行落库处理
List<Order> resultDatas = saveDatas(effectiveOrderList);
Long successNum = Long.valueOf(resultDatas.stream().filter(t -> t.getStatus().intValue() == STATUS_SUCCESS).collect(Collectors.toList()).size());
Long failNum = Long.valueOf(resultDatas.stream().filter(t -> t.getStatus().intValue() == STATUS_FAIL).collect(Collectors.toList()).size());
newTaskLog.setSuccessNum(newTaskLog.getSuccessNum()==null?successNum:newTaskLog.getSuccessNum()+successNum);
newTaskLog.setFailNum(newTaskLog.getFailNum()==null?failNum:newTaskLog.getFailNum()+failNum);
}



LambdaUpdateWrapper<TaskLog> updateWrapper=new LambdaUpdateWrapper<>();
updateWrapper.set(TaskLog::getCurTimeTaskMaxId, maxId);
updateWrapper.set(TaskLog::getSuccessNum,newTaskLog.getSuccessNum());
updateWrapper.set(TaskLog::getFailNum,newTaskLog.getFailNum());
updateWrapper.set(TaskLog::getTaskEndTime,DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
updateWrapper.set(TaskLog::getRepeatIds,newTaskLog.getRepeatIds());
updateWrapper.eq(TaskLog::getId, newTaskLog.getId());
taskLogMapper.update(null,updateWrapper);
        if( effectiveOrderList.size() < limitNum){
break;
}
}

// 更新为成功
LambdaUpdateWrapper<TaskLog> updateWrapper=new LambdaUpdateWrapper<>();
updateWrapper.set(TaskLog::getStatus, STATUS_SUCCESS);
updateWrapper.set(TaskLog::getTaskEndTime,DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
updateWrapper.eq(TaskLog::getId, newTaskLog.getId());
pushNikeDomsTaskLogMapper.update(null,updateWrapper);

log.info("Task 定时任务结束执行!");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值