数据分片,多线程处理

本文介绍了如何使用Java实现数据分片和多线程处理的场景。通过线程池工具类,模拟100w条数据并分页查询,每次处理1w条数据,进一步分为10个分片,每个分片由单独线程执行,提高数据导入Elasticsearch的效率。

场景:mysql有100w条数据,要根据id更新到Elasticsearch。

处理方式:100w 条数据分页查询,每次查询1w条数据,再将这1w条数据分成10个分片,每个分片1000条数据,每个分片的1000数据由一条线程去执行处理,相当于10个分片由10个线程处理。

 

线程池工具类

/**
 * 线程池工具类
 */
public class ThreadUtil {
    /**
     * 线程池实例
     */
    private static ThreadPoolExecutor executor = null;
    /**
     * 核心线程数
     */
    private static final Integer THREADPOOL_COREPOOLSIZE = 30;
    /**
     * 最大线程数
     */
    private static final Integer THREADPOOL_MAXPOOLSIZE = 200;
    /**
     * 线程等待回收的存活时间,单位:分钟
     */
    private static final long THREADPOOL_KEEPALIVETIME = 10;

    /**
     * 初始化线程池
     * 线程池拒绝策略为默认的拒绝策略,如果不能加入工作队列就抛出RejectedExecutionException异常
     */
    static {

        executor = new ThreadPoolExecutor(THREADPOOL_COREPOOLSIZE,
                THREADPOOL_MAXPOOLSIZE,
                THREADPOOL_KEEPALIVETIME,
                TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(80),
                new DefaultThreadFactory("defaultPool"),
                new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 使用线程池运行任务,线程无返回值
     * @param task
     */
    public static void execute(Runnable task){
        executor.execute(task);
    }

    /**
     * 使用线程池提交异步任务,任务运行带返回值
     * @param task 实现了Callable接口的线程
     * @return
     */
    public static Future submit(Callable task){
        return executor.submit(task);
    }

    /**
     * 停止线程池
     */
    public static void shutdown() {
        executor.shutdown();
    }

    public static ThreadPoolExecutor getExecutor(){
        return executor;
    }
}

 

模拟测试数据

public class ListFactory {

    private List<String> data;
    private int curPage = 0;
    private int pageSize = 20;

    public ListFactory() {
        String[] arr = new String[]{"9", "1", "2", "1", "3", "5", "8", "3", "9", "6", "8", "7", "5", "5", "1", "1", "5", "4", "9", "4", "2", "7", "8", "8", "5", "1", "4", "3", "8", "1", "2", "4", "2", "4", "4", "8", "8", "4", "4", "5", "3", "3", "6", "4", "7", "6", "5", "4", "1", "8", "4", "3", "6", "7", "7", "4", "5", "3", "9", "9", "5", "5", "6", "1", "4", "1", "9", "3", "5", "1", "1", "7", "9", "4", "9", "2", "8", "7", "6", "6", "4", "1", "6", "6", "9", "1", "5", "2", "1", "1", "8", "1", "6", "3", "1", "8", "4"};
        this.data = Arrays.asList(arr);
        System.out.println("全部数据 " + this.data);
    }

    public List<String> getNext() {
        int start = curPage * pageSize;

        //数据取完了
        if (start > data.size()) {
            return null;
        }
        int end = Math.min((start + pageSize), data.size());

        //下一页
        curPage++;
        return this.data.subList(start, end);
    }

    public int getSize() {
        return this.data.size();
    }

    public int getCurPage() {
        return this.curPage;
    }
    public static void main(String[] args) {
        ListFactory listFactory = new ListFactory();
        for (int i = 0; i < 10; i++) {
            List<String> data = listFactory.getNext();
            System.out.println(data);
        }
    }
}

 

执行的处理任务

public class MKTask implements Runnable {
    private final List<String> items;
    private int cur = 0;

    public MKTask(List<String> items) {
        this.items = items;
    }

    @Override
    public void run() {
        for (int j = 0; j < items.size(); j++) {
            try {
                //模拟业务处理耗时
                int s = 1 + (int) (Math.random() * 3);
                TimeUnit.SECONDS.sleep(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.cur = j;
        }
    }

    public int getCur() {
        return this.cur + 1;
    }

}

 

主线程分片

public class MultiThread {
    List<MKTask> taskList = new ArrayList<>();

    /**
     * 将任务切分,由多线程处理,并打印处理进度
     */
    @Test
    public void handleAll() throws InterruptedException {
        ListFactory listFactory = new ListFactory();
        int total = listFactory.getSize();

        while (true) {
            //获取翻页数据
            List<String> data = listFactory.getNext();
            //模拟取数据耗时
            TimeUnit.SECONDS.sleep(1);
            if (data == null) {
                break;
            }

            //每个线程处理 5 条
            int threadCount = 5;
            System.out.println("当前页" + listFactory.getCurPage());
            handlePageDate(data, threadCount);
        }

        //计算任务进度
        while (true) {
            TimeUnit.SECONDS.sleep(2);
            //每2秒打印一次各个线程的处理进度
            int sum = 0;
            for (MKTask mkTask : taskList) {
                int cur = mkTask.getCur();
                sum = sum + cur;
            }
            DecimalFormat dF = new DecimalFormat("0.00");
            float f = (float) sum / total;

            if (f == 1.0) {
                System.out.println("全部任务完成");
                break;
            }
            System.out.println("任务处理进度 " + dF.format(f * 100) + "%");
        }
    }

    public void handlePageDate(List<String> data, int threadCount) {
        int pageSize = data.size();

        //分成 segment 份,需要 segment 个线程处理
        int segment = (int) Math.ceil((double) pageSize / threadCount);

        System.out.println("当前页数据 " + data + " 一共有 " + pageSize + " 条, 每个线程处理 " + threadCount + " 条数据, 需要 " + segment + " 个线程处理");

        for (int i = 0; i < segment; i++) {
            int start = i * threadCount;
            int end = (i == segment - 1) ? pageSize : (start + threadCount);

            List<String> subList = data.subList(start, end);
            MKTask mkTask = new MKTask(subList);
            taskList.add(mkTask);
            System.out.println(start + " - " + end + " 范围数据 " + subList + " 交给一条线程处理");
            ThreadUtil.execute(mkTask);
        }
    }
}

 

执行结果

全部数据 [9, 1, 2, 1, 3, 5, 8, 3, 9, 6, 8, 7, 5, 5, 1, 1, 5, 4, 9, 4, 2, 7, 8, 8, 5, 1, 4, 3, 8, 1, 2, 4, 2, 4, 4, 8, 8, 4, 4, 5, 3, 3, 6, 4, 7, 6, 5, 4, 1, 8, 4, 3, 6, 7, 7, 4, 5, 3, 9, 9, 5, 5, 6, 1, 4, 1, 9, 3, 5, 1, 1, 7, 9, 4, 9, 2, 8, 7, 6, 6, 4, 1, 6, 6, 9, 1, 5, 2, 1, 1, 8, 1, 6, 3, 1, 8, 4]
当前页1
当前页数据 [9, 1, 2, 1, 3, 5, 8, 3, 9, 6, 8, 7, 5, 5, 1, 1, 5, 4, 9, 4] 一共有 20 条, 每个线程处理 5 条数据, 需要 4 个线程处理
0 - 5 范围数据 [9, 1, 2, 1, 3] 交给一条线程处理
5 - 10 范围数据 [5, 8, 3, 9, 6] 交给一条线程处理
10 - 15 范围数据 [8, 7, 5, 5, 1] 交给一条线程处理
15 - 20 范围数据 [1, 5, 4, 9, 4] 交给一条线程处理
当前页2
当前页数据 [2, 7, 8, 8, 5, 1, 4, 3, 8, 1, 2, 4, 2, 4, 4, 8, 8, 4, 4, 5] 一共有 20 条, 每个线程处理 5 条数据, 需要 4 个线程处理
0 - 5 范围数据 [2, 7, 8, 8, 5] 交给一条线程处理
5 - 10 范围数据 [1, 4, 3, 8, 1] 交给一条线程处理
10 - 15 范围数据 [2, 4, 2, 4, 4] 交给一条线程处理
15 - 20 范围数据 [8, 8, 4, 4, 5] 交给一条线程处理
当前页3
当前页数据 [3, 3, 6, 4, 7, 6, 5, 4, 1, 8, 4, 3, 6, 7, 7, 4, 5, 3, 9, 9] 一共有 20 条, 每个线程处理 5 条数据, 需要 4 个线程处理
0 - 5 范围数据 [3, 3, 6, 4, 7] 交给一条线程处理
5 - 10 范围数据 [6, 5, 4, 1, 8] 交给一条线程处理
10 - 15 范围数据 [4, 3, 6, 7, 7] 交给一条线程处理
15 - 20 范围数据 [4, 5, 3, 9, 9] 交给一条线程处理
当前页4
当前页数据 [5, 5, 6, 1, 4, 1, 9, 3, 5, 1, 1, 7, 9, 4, 9, 2, 8, 7, 6, 6] 一共有 20 条, 每个线程处理 5 条数据, 需要 4 个线程处理
0 - 5 范围数据 [5, 5, 6, 1, 4] 交给一条线程处理
5 - 10 范围数据 [1, 9, 3, 5, 1] 交给一条线程处理
10 - 15 范围数据 [1, 7, 9, 4, 9] 交给一条线程处理
15 - 20 范围数据 [2, 8, 7, 6, 6] 交给一条线程处理
当前页5
当前页数据 [4, 1, 6, 6, 9, 1, 5, 2, 1, 1, 8, 1, 6, 3, 1, 8, 4] 一共有 17 条, 每个线程处理 5 条数据, 需要 4 个线程处理
0 - 5 范围数据 [4, 1, 6, 6, 9] 交给一条线程处理
5 - 10 范围数据 [1, 5, 2, 1, 1] 交给一条线程处理
10 - 15 范围数据 [8, 1, 6, 3, 1] 交给一条线程处理
15 - 17 范围数据 [8, 4] 交给一条线程处理
任务处理进度 48.45%
任务处理进度 70.10%
任务处理进度 87.63%
任务处理进度 96.91%
全部任务完成

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值