關於定時任務中批量更新方案思考

可參考:

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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章