进行大规模数据迁移时,使用多线程,利用改进二分法,近似均等切分任务

来源:互联网 发布:nymex天然气 数据 编辑:程序博客网 时间:2024/05/22 15:45

楼主在进行数据迁移时遇到一个问题,即进行数据迁移时,需要用到多线程,提高写入速度,每个线程可以使用id来进行分割。例如假如有100条数据,id范围为0-99。分割为5个线程,可以这样:

线程1: id:0-20

线程2: id:20-40

线程3: id:40-60

线程4: id:60-80
线程5: id:80-99

但是实际情况往往是id分布并不均匀,假如有id范围为:0-100的数据总共有51条数据,0-50范围内可能有50条数据,50-100之间可能只有1条数据,此时按照上述方法会导致两个线程基本没有任何任务就跑完数据了,导致任务分布不均,降低了线程利用率。

该问题可以使用二分法来解决,我们先假定第一次均分点就在中间位置,50.利用数据库工具的cout操作统计出该范围内的数据量。楼主使用的是elasticsearch,能够在毫秒级别统计出响应结果(如果count操作十分耗时,不建议采用该方法)。

代码如下:

public class Task {    private long minId;    private long maxId;    public Task(long minId, long maxId) {        this.minId = minId;        this.maxId = maxId;    }    public long getMinId() {        return minId;    }    public void setMinId(long minId) {        this.minId = minId;    }    public long getMaxId() {        return maxId;    }    public void setMaxId(long maxId) {        this.maxId = maxId;    }}


public class EsTaskSheduleImpl implements TaskSheduleInterface {    public static final int THREAD_NUM = ESConfigure.THREAD_NUM;    //相等    private static final int COMPARE_EQ = 0;    //大于    private static final int COMPARE_GT = 1;    //小于    private static final int COMPARE_LE = -1;    public EsTaskSheduleImpl(String indexName, String typeName, String splitFieldName, Long start, Long end) {        this.indexName = indexName;        this.typeName = typeName;        this.splitFieldName = splitFieldName;        this.start = start;        this.end = end;    }    public EsTaskSheduleImpl() {    }    //要求Threa_NUM必须为2的指数倍    private static int getSplitTime() {        Double result = Math.log(THREAD_NUM) / Math.log(2);        return result.intValue();    }    public List<Task> getSplitTasks() {        List<Task> taskList = new ArrayList<Task>();        long totalSize = EsStatisticsTool.getCount(splitFieldName,indexName,typeName,start, end);        if(totalSize<=1000)//1000条数据以内使用单线程即可        {            Task task=new Task(start,end);            taskList.add(task);        }else {            taskList=getAverageSplitTasks();        }        return taskList;    }    /**     *作用: 获取均匀近似等分的任务     * splitPointsTreeSet 为有序、元素唯一的分割点集合,最开始有两个点,当要切分为2等份时,只需要切1次,4等份2次,8等份3次     * 对应关系即2^splitTime=Thread_NUM 因此Thread_Num必须为2的指数倍     * @return     */    private List<Task>getAverageSplitTasks()    {        List<Task> taskList = new ArrayList<Task>();        EsTaskSheduleImpl taskShedule = new EsTaskSheduleImpl();        TreeSet<Long> splitPointsTreeSet = new TreeSet();        splitPointsTreeSet.add(start);        splitPointsTreeSet.add(end);        //获取要切分次数        final int splitTime = getSplitTime();        for (int i = 0; i < splitTime; i++) {            Long[] array = splitPointsTreeSet.toArray(new Long[splitPointsTreeSet.size()]);            int arrayEnd = array.length - 1;            for (int j = 0; j < arrayEnd; j++) {                Long middle = taskShedule.findSimilarMedianByBinarySearch(array[j], array[j + 1]);                splitPointsTreeSet.add(middle);            }        }        Long[] array = splitPointsTreeSet.toArray(new Long[splitPointsTreeSet.size()]);        for (int i = 0; i < array.length - 1; i++) {            taskList.add(new Task(array[i], array[i + 1]));        }        return taskList;    }    /**     * 使用非递归二分查找的方式,在两条数据之间,查找数据量均分的近似点     *     * @return     */    public long findSimilarMedianByBinarySearch(long low, long high) {        //标识位置,任何比较都是与起始位置的比较        long start = low;        long totalSize = EsStatisticsTool.getCount(splitFieldName,indexName,typeName,low, high);        long halfSize = totalSize >> 1;        long middle = -1;        while (low <= high) {            //中间位置计算,low+ 最高位置减去最低位置,右移一位,相当于除2.也可以用(high+low)/2            middle = low + ((high - low) >> 1);            long actualValue = EsStatisticsTool.getCount(splitFieldName, indexName, typeName, start, middle);            int compareValue = this.compare(actualValue, halfSize);            //与最中间的数字进行判断,是否相等,相等的话就返回对应的数组下标.            if (compareValue == COMPARE_EQ) {                return middle;                //如果小于的话则移动最低层的"指针"            } else if (compareValue == COMPARE_LE) {                low = middle + 1;            } else {                high = middle - 1;            }        }        return middle;    }    /**     * 判断是否找到均分点的依据,当误差不大于2%,或者数据量相差不到200时,认为达到找到均分点,返回0,超出该范围则认为     * 尚未找到均分点,返回1,或者-1     *     * @param actualValue     * @param idealValue     * @return     */    private int compare(long actualValue, long idealValue) {        //差值        long space = actualValue - idealValue;        //差值绝对值        long absoluteSpace = Math.abs(space);        //误差百分比        long errorRatio = (absoluteSpace * 100 / idealValue);        if ((errorRatio <= 2) || (absoluteSpace <= 200))            return COMPARE_EQ;        else if (space > 0)            return COMPARE_GT;        else            return COMPARE_LE;    }}


线程2: id:20-40
线程2: id:20-40
0 0
原创粉丝点击