可參考:
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>