关于定时任务中批量更新方案思考

可参考:

https://www.cnblogs.com/ShaYeBlog/p/5762553.html

https://blog.csdn.net/li396864285/article/details/53607536

一,场景:

定时任务需要从中间表同步数据到业务表,然后更新中间表状态为已同步。

二,处理方案:

【分批处理】每次查询500条数据,对500条数据批量写入业务表,批量更新中间表状态

【循环处理】每次执行定时任务,通过do while,条件是只要能够查到中间表存在未同步的数据,就执行同步操作。这样好处:

每次跑定时任务可以尽可能的多的处理数据,甚至可以处理完一段时间内写入中间表的所有未同步数据。

可能会有疑问,如果把定时任务执行周期缩短,不也可以快速处理数据。这两种方案可以综合使用。

可能又有疑问,执行周期过短,会导致上一个任务还没处理完成,下一个任务就开始进行业务处理,引发并发问题,数据被重复获取处理。在现在这种场景,中间表数据会被重复获取,重复写入业务表,重复更新中间表。对于业务表重复写入,因为根据有唯一索引,存在更新,不存在写入,并不会导致错误;对于更新中间表,每次只是更新状态已完成,重复更新也没有问题。

其他不同场景,执行周期过短可能会引发严重问题。之前有种场景是,在仓储系统中,定时取原始数据组装成拣货单,导致原始数据被重复获取,导致重复组单,这时需要考虑业务的执行时间与定时任务的执行周期,同时考虑加分布式锁。

三,疑问:

1,每批处理500基础什么原因考虑,多条更合适,如果数据量过大引发什么问题?对于查询,可能会很慢,对于更新,可能会锁表,还是行级锁,出现锁定问题,有引发其他什么问题?

2,使用do while是否可以避免,执行周期过短导致的并发问题

因为先执行do内容,在判断条件,do中业务执行完毕只有才会进行下一次条件判断。不会存在中间表中未同步数据被重复取到的问题。

四,定时任务实现

使用的当当网的elastci-job

五,代码实现

1,定时任务类

@Component
@Slf4j
@ElasticSimpleJob(taskName = "syncBaseDeliveryAreaConfigFreshJob")
public class SyncBaseDeliveryAreaConfigFreshJob implements SimpleJob {

    @Resource
    private BaseDeliveryAreaConfigFreshService baseDeliveryAreaConfigFreshService;

    @Override
    public void execute(ShardingContext shardingContext) {
        log.info("SyncBaseDeliveryAreaConfigFreshJob start:{}", shardingContext);
        try {
            List<MidDeliveryAreaConfigFresh> list;
            do {
                list = baseDeliveryAreaConfigFreshService.selectPendingDataList(BizConstant.SPLIT_NUM);
                baseDeliveryAreaConfigFreshService.syncBaseDeliveryAreaConfigFresh(list);
            }
            while (CollectionUtils.isNotEmpty(list));
        } catch (Exception e) {
            log.error("SyncBaseDeliveryAreaConfigFreshJob  exception!:{}", e);
        }
        log.info("SyncBaseDeliveryAreaConfigFreshJob end");
    }
}

2,业务实现类

    @Transactional
    @Override
    public void syncBaseDeliveryAreaConfigFresh(List<MidDeliveryAreaConfigFresh> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        // 取配送区域名称,取中间表,取结果表可能未同步
        List<String> deliveryAreaCodeList = list.stream().map(MidDeliveryAreaConfigFresh::getDeliveryAreaCode).distinct().collect(Collectors.toList());
        List<MidDeliveryAreaFresh> midDeliveryAreaFreshList = midDeliveryAreaFreshMapper.queryByDeliveryAreaCodes(deliveryAreaCodeList);
        Map<String, String> map = midDeliveryAreaFreshList.stream().collect(Collectors.toMap(v -> v.getDeliveryAreaCode(), v -> v.getDescription(), (a, b) -> b));

        // 根据结果表唯一索引分组,取中间表重复最新一条
        Map<String, List<MidDeliveryAreaConfigFresh>> mapGroup = list.stream().collect(Collectors.groupingBy(v ->
                v.getDcCode() + v.getBigCategoryCode() + v.getSmallCategoryCode() + v.getProductCode() + v.getStockLoc()));
        List<MidDeliveryAreaConfigFresh> resultList = mapGroup.values().stream().map(listv -> listv.get(listv.size() - 1)).collect(Collectors.toList());

        // 将创建时间最大的数据插入或者更新到结果表
        baseDeliveryAreaConfigFreshMapper.saveOrUpdateBatch(conventResult(resultList, map));

        // 将中间表此批次处理的所有数据状态置为已同步
        List<Long> ids = list.stream().map(MidDeliveryAreaConfigFresh::getId).collect(Collectors.toList());
        midDeliveryAreaConfigFreshMapper.updateProcessFlagByIds(ids);

    }

    @Override
    public List<MidDeliveryAreaConfigFresh> selectPendingDataList(int num) {
        return midDeliveryAreaConfigFreshMapper.selectPendingDataList(num);
    }

3,批量更新or写入业务表

注意判断维度是什么,表的唯一索引:

void saveOrUpdateBatch(@Param("list") List<BaseDeliveryAreaConfigFresh> baseDeliveryAreaConfigFreshList);

 <insert id="saveOrUpdateBatch">
        insert into base_delivery_area_config_fresh (
        dc_code, stock_loc_code, delivery_area_code, delivery_area_name, small_category_code,
        big_category_code, product_code, is_delete, created_time, created_by, updated_time,
        updated_by
        )values
        <foreach collection="list" item="item" separator=",">
            (
            #{item.dcCode,jdbcType=VARCHAR},
            #{item.stockLocCode,jdbcType=VARCHAR},
            #{item.deliveryAreaCode,jdbcType=VARCHAR},
            #{item.deliveryAreaName,jdbcType=VARCHAR},
            #{item.smallCategoryCode,jdbcType=VARCHAR},
            #{item.bigCategoryCode,jdbcType=VARCHAR},
            #{item.productCode,jdbcType=VARCHAR},
            #{item.isDelete,jdbcType=INTEGER},
            #{item.createdTime,jdbcType=TIMESTAMP},
            #{item.createdBy,jdbcType=VARCHAR},
            #{item.updatedTime,jdbcType=TIMESTAMP},
            #{item.updatedBy,jdbcType=VARCHAR}
            )
        </foreach>
        ON DUPLICATE KEY UPDATE
        `dc_code` = VALUES(`dc_code`),
        `stock_loc_code` = VALUES(`stock_loc_code`),
        `delivery_area_code` = VALUES(`delivery_area_code`),
        `delivery_area_name` = VALUES(`delivery_area_name`),
        `small_category_code` = VALUES(`small_category_code`),
        `big_category_code` = VALUES(`big_category_code`),
        `product_code` = VALUES(`product_code`),
        `is_delete` = VALUES(`is_delete`),
        `created_time` = VALUES(`created_time`),
        `updated_time` = VALUES(`updated_time`),
        `created_by` = VALUES(`created_by`),
        `updated_by` = VALUES(`updated_by`)
    </insert>
</mapper>

4,更新中间表

void updateProcessFlagByIds(@Param("ids") List<Long> ids);

<update id="updateProcessFlagByIds">
  update mid_delivery_area_config_fresh set process_flag = 1
  where id in
  <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
  </foreach>
</update>

 

在 Django 实现定时任务的邮件批量发送,可以使用 Celery 和 Django-celery-beat 来完成。 首需要安装 Celery 和 Django-celery-beat,可以通过 pip install celery django-celery-beat 命令进行安装。 接着,需要在 Django 项目配置 Celery,具体步骤如下: 1. 在 settings.py 文件添加以下配置: ``` CELERY_BROKER_URL = 'amqp://guest:guest@localhost:5672//' # RabbitMQ 的连接地址 CELERY_RESULT_BACKEND = 'rpc://' # 结果存储方式,这里使用 RPC CELERY_TIMEZONE = 'Asia/Shanghai' # 时区 CELERY_TASK_SERIALIZER = 'json' # 任务序列化方式 CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方式 CELERY_ACCEPT_CONTENT = ['json'] # 接受的内容类型 ``` 2. 在项目创建一个 tasks.py 文件,用于编写定时任务的代码。例如,下面的代码实现了每天定时发送邮件的功能: ``` from celery import task from django.core.mail import send_mail from datetime import datetime, timedelta @task def send_daily_email(): today = datetime.now().strftime('%Y-%m-%d') subject = f'今日邮件主题{today}' message = '今日邮件内容' from_email = '发件人邮箱' recipient_list = ['收件人邮箱1', '收件人邮箱2'] send_mail(subject, message, from_email, recipient_list) ``` 3. 在 settings.py 文件添加以下配置,用于设置定时任务的调度时间: ``` CELERY_BEAT_SCHEDULE = { 'send_daily_email': { 'task': 'tasks.send_daily_email', 'schedule': timedelta(days=1), }, } ``` 这里的 'schedule': timedelta(days=1) 表示每天执行一次定时任务。 最后,在命令行启动 Celery 任务调度器和 Celery worker,命令如下: ``` celery -A your_project_name worker -l info celery -A your_project_name beat -l info ``` 这样就完成了在 Django 实现定时任务的邮件批量发送的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值