工作中经常会遇到需要增量拉取指定时间间隔第三方接口数据的需求,可以通过缓存上一次同步数据时间的方式来拉取数据,建议定时任务支持传递指定参数的形式触发做补偿重试
以增量同步经销商网点为例:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyncErpShopParams {
/**
* 经销商编码
*/
private List<String> dealerCodes;
/**
* 开始时间,格式:yyyy-MM-dd HH:mm:ss
*/
private String startTime;
/**
* 结束时间,格式:yyyy-MM-dd HH:mm:ss
*/
private String endTime;
}
@XxlJob(value = "syncErpShopJob")
public void syncErpShopJob() throws Exception {
SyncErpShopParams params = new SyncErpShopParams();
String jobParam = XxlJobHelper.getJobParam();
if(StringUtils.isNotBlank(jobParam)) {
params = JSONObject.parseObject(jobParam, SyncErpShopParams.class);
}
String startTime = params.getStartTime();
String endTime = StringUtils.isNotBlank(params.getEndTime()) ? params.getEndTime() : DateUtil.formatDateTime(new Date());
List<String> dealerCodes = params.getDealerCodes();
if(CollectionUtils.isEmpty(dealerCodes)) {
dealerCodes = dealerMapper.findOnLineDealers();
}
for(String dealerCode : dealerCodes) {
if(StringUtils.isBlank(params.getStartTime())) {
String lastSyncDate = (String) redisService.hGet(RedisConstants.SYNC_ERP_SHOP_KEY, dealerCode);
startTime = StringUtils.isNotBlank(lastSyncDate) ? lastSyncDate : "1979-01-01 00:00:00";
}
//调用第三方接口同步数据逻辑...
erpShopService.queryAndSaveShop(dealerCode, startTime, endTime);
if(StringUtils.isBlank(params.getStartTime()) && StringUtils.isBlank(params.getEndTime())) {
redisService.hSet(RedisConstants.SYNC_ERP_SHOP_KEY, dealerCode, endTime);
}
}
}
但由于网络闪断或接口不稳定等因素,可能会接口调用失败造成数据丢失,一般有以下方法可以补偿
一、断补
本次任务拉取失败后,不更新同步数据时间,下次任务的开始时间=上一次同步数据时间,结束时间=当前时间。例如:
8点开始执行任务:拉取2023-02-14 07:00:00~2023-02-14 08:00:00数据,同步失败
9点开始执行任务:拉取2023-02-14 07:00:00~2023-02-14 09:00:00数据。此时7点~8点的数据会自动补上
- 优点:拉取失败后无需处理重试,下次任务会自动补上
- 缺点:如果失败持续时间较长,会积攒越来越多的数据没有拉取到,服务恢复后拉取的数据量大
二、后补
本次任务拉取失败后,依然更新同步数据时间,将同步失败的时间参数报警推送出来并记录下来,通过补偿任务重试或手动重试。下次任务的开始时间=上一次同步数据时间,结束时间=当前时间。例如:
8点开始执行任务:拉取2023-02-14 07:00:00~2023-02-14 08:00:00数据,同步失败,更新同步数据时间为2023-02-14 08:00:00,通过补偿任务重试或手动重试7点~8点的数据
9点开始执行任务:拉取2023-02-14 08:00:00~2023-02-14 09:00:00数据
- 优点:失败的任务单独处理,不影响正常逻辑,重试拉取的数据量在一定范围、不会很大
- 缺点:需要记录失败请求、增加补偿任务或人为关注失败情况并进行重试
CREATE TABLE `api_client_error_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键,自动生成',
`url` varchar(500) NOT NULL DEFAULT '' COMMENT '请求地址',
`header_params` varchar(5000) NOT NULL DEFAULT '' COMMENT '请求头参数',
`params` varchar(5000) NOT NULL DEFAULT '' COMMENT '请求参数',
`return_message` text COMMENT '返回参数',
`exception_message` varchar(5000) NOT NULL DEFAULT '' COMMENT '异常信息',
`log_type` int NOT NULL DEFAULT '0' COMMENT '对应枚举ApiClientErrorLogTypeEnum',
`success_type` int NOT NULL DEFAULT '0' COMMENT '最终成功状态 0-失败 1-成功',
`error_count` int NOT NULL DEFAULT '0' COMMENT '失败次数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_success_type_error_count` (`success_type`,`error_count`),
KEY `idx_log_type` (`log_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方接口调用异常记录表';
补偿任务查询sql:
select * from api_client_error_log where success_type = 0 and error_count < 3;
推荐一个不错的动态记录操作日志组件 bizlog-sdk:爽!一个注解,搞定 SpringBoot 操作日志