批处理任务的设计处理思路小记

1.概述

我们在日常开发中经常会遇到需要进行定时批处理的需求,也就是所谓的跑批,本文记录下简单批处理任务的开发思路。

2.数据模型设计

批处理任务本质是指在指定的时间,由应用程序对源数据(需要处理的数据)进行业务处理,产生业务需要的目标数据的过程。所以数据模型(也就是表结构)的设计是至关重要的;不论什么业务场景,核心是数据模型可以记录批处理的一些简单结果,常见的源数据表结构中都会存在如下通用字段:
process_status: 处理状态(0:未处理, 1:处理成功, 2处理失败)
fail_msg: 失败原因
如:

CREATE TABLE `xxx_origin` (
    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
    `batch_no` VARCHAR(16) NOT NULL COMMENT '批次号',
    `process_status` INT(11) NOT NULL COMMENT '处理状态, 0:未处理, 1:处理成功, 2:处理失败',
    `fail_msg` VARCHAR(256) NOT NULL COMMENT '处理失败的原因',
    `create_by` INT(11) NOT NULL DEFAULT '0' COMMENT '创建人',
    `create_dt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_by` INT(11) NOT NULL DEFAULT '0' COMMENT '更新人',
    `update_dt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    INDEX `idx_batch_no` (`batch_no`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 AUTO_INCREMENT=1 COLLATE='utf8mb4_general_ci' COMMENT='xxx表';

目标表各不相同,一般会有批次号字段batch_no

3.开发

进行coding时,核心就是你要仔细考虑跑批时处理的数据量级;如果数据量只是百级、千级很少的数据量,那么你实际并不需要考虑性能问题,跑批时直接加载需要处理的数据,一次性处理即可,简单快捷,也不会有性能问题;如果数据量上万、上百万、千万的量级,那么,此时,跑批时的处理策略就异常重要了。简单有效的策略是跑批时分页加载需要处理的数据当然,具体落实这个策略时,也有很多需要注意的点:
1.直接使用分页sql在大数据量时查询速度非常慢,如mysql的limit
2.事务的处理
既然是问题,当然就有解决的方法,跑批时相关处理代码如下:

    @Transactional(rollbackFor = Exception.class)
    public void run(String batchNo) {
        //分页处理该批次未处理的数据
       while (true) {
            //获取该批次未处理的第一条数据id
            Integer startIndex = originDataService.getFirstUnprocessedItemId(batchNo);
            log.info("startIndex: {}", startIndex);
            if (Objects.isNull(startIndex)) {
                log.info("当前批次数据处理结束 batchNo: {}", batchNo);
                break;
            }

            //获取未处理的数据
            List<OriginDataPO> dataList = originDataService.getUnprocessedItemsByBatchNo(startIndex, BATCH_SIZE, batchNo);
            try {
                log.info("待处理dataList: {}", dataList);
                originDataCollector.doCollect(batchNo, dataList);
            } catch (Exception e) {
                log.error("数据处理失败", e);
                //更新数据明细处理状态为:处理失败
                List<Integer> targetIds = logPOList.stream().map(m -> m.getId()).collect(Collectors.toList());
                originDataService.updateOriginDataItemOnFail(targetIds, e.getMessage());
            }
       }
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void doCollect(final String batchNo, List<OriginDataPO> dataList) {
		//事务处理:新开事务,保证每一页的数据处理结束后都提交事务保存相关数据,这样可以避免经典的最后一条失败了,之前处理的都要回滚
		......
		//业务逻辑处理
	}

	@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void updateOriginDataItemOnFail(List<Integer> ids, String errMsg) {
		//事务处理:新开事务
		......
		//记录失败明细
	}

代码执行流程:

在这里插入图片描述

4.优化

上述代码中,针对传统的分页做了优化,核心就是使用覆盖索引替换limit,这样可以大大提高大数据量的查询速度,详细如下:
上述代码中originDataService.getFirstUnprocessedItemId(batchNo)对应的SQL语句为:

	@Options(flushCache = Options.FlushCachePolicy.TRUE)
    @Select("SELECT id FROM xxx_origin_data WHERE batch_no = #{batchNo} AND process_status = 0 ORDER BY id ASC LIMIT 1;")
    Integer selectFirstUnprocessedItem(@Param("batchNo") String batchNo);

上述代码中originDataService.getUnprocessedItemsByBatchNo(startIndex, BATCH_SIZE, batchNo)对应的SQL语句为:

	@Select("SELECT id AS id, merchant_no AS merchantNo, batch_no AS batchNo, channel AS channel, product_id AS productId, " +
            "property_id AS propertyId, process_status AS processStatus, merchant_name AS merchantName " +
            "FROM xxx_origin_data " +
            "WHERE id >= #{start} AND batch_no = #{batchNo} AND process_status = 0 ORDER BY id ASC LIMIT #{offset};")
    List<RiskMerchantLogPO> selectUnprocessedItems(@Param("start") Integer start, @Param("batchNo") String batchNo, @Param("offset") Integer offset);

5.结语

对于复杂的跑批任务,例如银行业中清分系统、计费系统、结算系统等等,我们可以使用专门的批处理框架,如SpringBatch,可以支撑更复杂、细粒度的控制。跑批任务的调度可以使用目前流行的xxlJob框架。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值