多线程导入数据(目前数据量在上百万左右)

本文介绍了一种利用Spring Boot框架下的线程池和jdbcTemplate进行多线程批量数据库插入的方法,通过配置线程池参数,实现高效的数据处理,特别适用于大数据量的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程导出excel

公司项目需求,需要生成大量数据存入数据库,刚开始使用mybatis 批量插入,但是后来通过查资料发现,相对有spring 的jdbcTemplate处理速度,mybatis还是有些慢,后来就自己采用jdbcTemplate,并采用多线程分批插入

配置线程池

项目使用springboot框架,所以线程池也是用springboot配置

@Configuration
@EnableAsync
public class TaskPoolConfig {

    @Bean("taskExecutor")
    public Executor taskExecutro(){
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("系统最大线程数  : "+i);
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(i);
        taskExecutor.setMaxPoolSize(i);
        taskExecutor.setQueueCapacity(99999);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("taskExecutor--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }
}

定义jdbcTemplate批量插入

@Repository
public class CodeRepository {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;

    public void batchSave ( List<Code> list){

        SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(list.toArray());

        String sql=" insert into two_dimensional_code_service.code(" +
                "id, code, create_time, status, type, brand_number, code_apply_id, code_apply_detail_id)" +
                " values (:id, :code, :createTime,:status, :type,:brandNumber, :codeApplyId, :codeApplyDetailId)";
        jdbcTemplate.batchUpdate(sql, (batch));

    }

    public void batchUpdate ( List<Code> list ,String applyDetailsId){

        SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(list.toArray());

        String sql=" UPDATE two_dimensional_code_service.code SET status=1 , factory_plan_id= '"+applyDetailsId+"' where id=:id" ;

        jdbcTemplate.batchUpdate(sql, (batch));
    }

}

定义异步执行方法

 @Component
@Slf4j
public class AsyncTaskService {
    @Autowired
    private CodeRepository codeRepository;

    /**
     * 
     * @param map 要处理的批次数据分页信息
     * @param cdl
     */
    @Async("taskExecutor")
    public void generateCode(Map<String, Object> map , CountDownLatch cdl) {
        long start = System.currentTimeMillis();
        // 导出文件路径
        List<Code> list = new ArrayList<>();
        int pageSize = (int) map.get("pageSize");
        int type = (int) map.get("type");
        CodeApplyDetails codeApplyDetail = (CodeApplyDetails)map.get("codeApplyDetail");
        try {
            for (int i = 0; i < pageSize; i++) {
                Code code = new Code();
                code.setId(UUIDUtils.getUUID());
                code.setCode(UUIDUtils.getUUID());
                code.setCreateTime(new Date());
                code.setBrandNumber(codeApplyDetail.getTobaccoName());
                code.setCodeApplyDetailId(codeApplyDetail.getId());
                code.setCodeApplyId(codeApplyDetail.getApplyId());
                code.setStatus(0);
                code.setType(type);
                code.setCycle(codeApplyDetail.getCycle());
                list.add(code);
            }
            codeRepository.batchSave(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        log.info("线程:" + Thread.currentThread().getName() + "第   " + map.get("page") + "   批次 , 插入   " + map.get("pageSize") + "   条成功 ,耗时 :" + (end - start));
        list.clear();
        cdl.countDown();
    }
}

service层

这里使用mybatis操作数据库,根据总的数据量处理每批次的分页信息存入队列,然后每从队列取出一个批次信息开启一个线程,调用异步导出方法,

@Service("codeReceptionListService")
@Slf4j
public class CodeServiceImpl implements CodeService {
    @Resource
    private CodeDao codeDao;
    @Autowired
    private AsyncTaskService asyncTaskService;

    @Resource
    private CodeApplyDao codeApplyDao;


    public static Queue<Map<String, Object>> queue;//Queue是java自己的队列,具体可看API,是同步安全的

    static {
        queue = new ConcurrentLinkedQueue<Map<String, Object>>();
    }

    @Resource
    private CodeApplyDetailsDao codeApplyDetailsDao;

    /**
     * 生成编码
     *
     * @param applyId 申请表id
     */
    @Override
    @Transactional
    public void generateCode(String applyId)  {
        //获取申请表明细
        HashMap<String, Object> map = new HashMap<>();
        map.put("applyId", applyId);
        map.put("limit", 9999);
        map.put("page", 1);
        PageUtil.pageUtil(map);
        List<CodeApplyDetails> codeApplyDetails = codeApplyDetailsDao.queryAllByLimit(map);

        for (CodeApplyDetails codeApplyDetail : codeApplyDetails) {
            initQueue(codeApplyDetail, 0);
            initQueue(codeApplyDetail, 1);
            initQueue(codeApplyDetail, 2);
            Integer pieceNumber = codeApplyDetail.getPieceNumber();
            Integer barNumber = codeApplyDetail.getBarNumber();
            Integer boxNumber = codeApplyDetail.getBoxNumber();
            log.info("总件数 :  " + pieceNumber.toString());
            log.info("总条数 :  " + barNumber.toString());
            log.info("总包数 :  " + boxNumber.toString());

        }
        long start = System.currentTimeMillis();
        int codeingStatus=0;
        try {
        CountDownLatch cdl = new CountDownLatch(queue.size());
        while (queue.size() > 0) {
            asyncTaskService.generateCode(queue.poll(), cdl);
        }
            cdl.await();
        } catch (Exception e) {
            e.printStackTrace();
            codeingStatus=2;
        }finally {
            CodeApply codeApply = new CodeApply();
            codeApply.setId(applyId);
            codeApply.setCodeingStatus(codeingStatus);
            codeApplyDao.update(codeApply);
        }
        long end = System.currentTimeMillis();
        log.info("任务执行完毕       共消耗   :  "+(end-start)/1000+"  秒");
        //明细表分别生成件 、条、包对应的码


    }

    /**
     * 初始化队列
     */
    public void initQueue(CodeApplyDetails codeApplyDetail, int type) {
        List<Code> codes = new ArrayList<>();
        if (codeApplyDetail.getIsCodeing() > 0 && codeApplyDetail.getIsCodeingPiece() > 0) {
            int pageSize = 10000;
            Integer count = 0;
            if (type == 0) {
                count = codeApplyDetail.getPieceNumber();
            }
            if (type == 1) {
                count = codeApplyDetail.getBarNumber();
            }
            if (type == 2) {
                count = codeApplyDetail.getBoxNumber();
            }
            int pages = count / pageSize + (count % pageSize > 0 ? 1 : 0);
            //int pages = pieceNumber / pageSize;

            for (int i = 1; i <= pages; i++) {
                List<Map<String, Object>> list = new ArrayList<>(4);
                Map<String, Object> map = new HashMap<>();
                //判断是否是最后一页
                if (i == pages) {
                    pageSize = count - (pageSize * (pages - 1));
                    //每页多上条
                }
                map.put("pageSize", pageSize);
                map.put("page", i);
                map.put("type", type);
                map.put("codeApplyDetail", codeApplyDetail);
                //添加元素
                queue.offer(map);
            }
        }
    }
}
### 高效传输大容量数据集至服务器的最佳实践 #### 使用压缩技术减少文件大小 为了提高传输效率并降低带宽消耗,在上传之前应先对大型数据集应用有效的压缩算法。常见的做法是对整个目录创建一个tarball档案,随后利用gzip或xz等工具进一步缩小其体积[^1]。 ```bash tar -cvf dataset.tar ./dataset_directory/ pigz -p 8 -9 dataset.tar # 并行压缩 tar 文件 ``` #### 实施分片策略以便于管理与恢复 对于特别庞大的单体文件而言,将其分割成多个较小部分有助于简化操作流程以及增强容错能力。一旦某个片段发送失败,则只需重新传送该特定切片而非全部重来。rsync命令支持增量同步功能,能够只复制更改过的区块;split实用程序允许用户指定每份子件的最大尺寸。 ```bash split --bytes=1GB --numeric-suffixes=1 largefile.bin chunk_ scp chunk_* user@remote:/path/to/destination/ ssh user@remote 'cat chunk_* > reassembled_largefile.bin' rm chunk_* ``` #### 利用专用协议优化网络性能 FTP/SFTP虽然简单易用但是并不总是最优选择。GridFTP专为科学计算设计,具备多线程读写特性从而加快远距离跨国界的数据交换速度。Aspera同样表现出众特别是在处理海量多媒体资源方面拥有独特优势,它采用了专利FASP™加速机制确保稳定高速度连接即使在网络状况不佳的情况下也能保持良好表现。 #### 构建分布式存储架构实现负载均衡 当面对PB级别以上的超大规模资料库迁移需求时,考虑部署对象存储服务(如Amazon S3、Google Cloud Storage)或者搭建Ceph集群作为临时缓存节点。这些方案不仅提供了近乎无限扩展性的空间而且内置冗余保护措施防止意外丢失重要信息。通过API接口可以直接对接应用程序完成自动化批量导入导出作业而无需人工干预。 #### 应用差量备份节约时间成本 如果目标机器已经保存有旧版本副本的话,那么仅需更新新增加的内容即可大大缩短整体耗时。Git LFS就是一个很好的例子,专门针对二进制大文件提供高效的版本控制解决方案。另外还有Rsync这类经典工具也可以很好地胜任这项工作,只需要配置好排除规则就能精准定位差异之处快速同步修改后的记录。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值