hasoop mapreduce неупорядоченный кортеж в качестве ключа карты

1

Основываясь на примере wordcount из Hadoop - The Definitive Guide, я разработал задание mapreduce для подсчета появления неупорядоченных кортежей строк. Ввод выглядит так (просто больше):

аб
куб.см
дд
ба
объявление
дд

Запуск mapreduce Я ожидаю, что выход будет (для этого примера):

cc 1
дд 1
ab 2
объявление 1
дд 1

Это означает, что я хочу, чтобы кортежи a, b и b, a считались одинаковыми. Вопрос уже задан здесь: Hadoop MapReduce: два значения в качестве ключа в Mapper-Reducer и, вероятно, были решены здесь https://developer.yahoo.com/hadoop/tutorial/module5.html#keytypes.

Для больших входных файлов я получаю такой вывод, первый столбец - hashCode of resp. ключ:

151757761 aa 62822
153322274 ab 62516
154886787 ac 62248
156451300 объявление 62495
153322274 ba 62334
154902916 bb 62232
158064200 bd 62759
154886787 ca 62200
156483558 cb 124966
158080329 cc 62347
159677100 dc 125047
156451300 да 62653
158064200 db 62603
161290000 dd 62778

Как можно видеть, некоторые ключи являются дубликатами, например 153322274 для a, b и b, a. Для других, таких как c, b (и b, c) и c, d (и d, c), счет правильный. Примерно вдвое больше, чем другие, потому что тестовые данные рисуются равномерно случайным образом.

Я искал эту проблему в течение некоторого времени и теперь исчерпываю идеи, почему все еще могут быть ключевые дубликаты после фазы сокращения.

Ниже приведен код, который я использую:

Сначала код для моего пользовательского WritableComparable

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableUtils;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.math.BigInteger;

public class Pair implements WritableComparable<Pair> {

    private String first;
    private String second;

    public Pair(String first, String second) {
        this.first = first;
        this.second = second;
    }

    public Pair() {
       this("", "");
    }

    @Override
    public String toString() {
        return this.hashCode() + "\t" + first + "\t" + second;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        WritableUtils.writeString(out, first);
        WritableUtils.writeString(out, second);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        first = WritableUtils.readString(in);
        second = WritableUtils.readString(in);
    }

    @Override
    public int hashCode() {
        BigInteger bA = BigInteger.ZERO;
        BigInteger bB = BigInteger.ZERO;

        for(int i = 0; i < first.length(); i++) {
            bA = bA.add(BigInteger.valueOf(127L).pow(i+1).multiply(BigInteger.valueOf(first.codePointAt(i))));
        }

        for(int i = 0; i < second.length(); i++) {
            bB = bB.add(BigInteger.valueOf(127L).pow(i+1).multiply(BigInteger.valueOf(second.codePointAt(i))));
        }

        return bA.multiply(bB).intValue();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof Pair) {
            Pair other = (Pair) o;

            boolean result = ( first.compareTo(other.first) == 0 && second.compareTo(other.second) == 0 )
                    || ( first.compareTo(other.second) == 0 && second.compareTo(other.first) == 0 );

            return result;
        }
        return false;
    }

    @Override
    public int compareTo(Pair other) {
        if (( first.compareTo(other.first) == 0 && second.compareTo(other.second) == 0 )
                || ( first.compareTo(other.second) == 0 && second.compareTo(other.first) == 0 ) ) {
            return 0;
        } else {
            int cmp = first.compareTo( other.first );

            if (cmp != 0) {
                return cmp;
            }

            return second.compareTo( other.second );
        }
    }
}

И остальное:

import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class PairCount {

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();

        if (otherArgs.length < 2) {
            System.err.println("Usage: paircount <in-dir> <out-dir>");
            System.exit(2);
        }

        Job job = new Job(conf, "word count");
        job.setJarByClass(PairCount.class);

        job.setMapperClass(TokenizerMapper.class);
        job.setReducerClass(IntSumReducer.class);

        job.setMapOutputKeyClass(Pair.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setOutputKeyClass(Pair.class);
        job.setOutputValueClass(IntWritable.class);

        for (int i = 0; i < otherArgs.length - 1; ++i) {
            FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
        }

        FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1]));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }

    public static class TokenizerMapper extends Mapper<Object, Text, Pair, IntWritable> {

        private final static IntWritable one = new IntWritable(1);

        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            StringTokenizer itr = new StringTokenizer(value.toString());

            while (itr.hasMoreTokens()) {
                context.write(new Pair(itr.nextToken(), itr.nextToken()), one);
            }
        }
    }

    public static class IntSumReducer extends Reducer<Pair, IntWritable, Pair, IntWritable> {
        private IntWritable result = new IntWritable();

        public void reduce(Pair key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;

            for (IntWritable val : values) {
                sum += val.get();
            }

            result.set(sum);
            context.write( key, result);
        }
    }
}

Edit: Я добавил модульные тесты для функций hashCode() и compareTo(). Они отлично работают.

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

public class Tests  {
    @Test
    public void testPairComparison() {
        assertTrue( 0 == new Pair("a", "a").compareTo(new Pair("a", "a")) );
        assertTrue( 0 == new Pair("a", "b").compareTo(new Pair("b", "a")) );
        assertTrue( 0 == new Pair("a", "c").compareTo(new Pair("c", "a")) );
        assertTrue( 0 == new Pair("a", "d").compareTo(new Pair("d", "a")) );

        assertTrue( 0 == new Pair("b", "b").compareTo(new Pair("b", "b")) );
        assertTrue( 0 == new Pair("b", "c").compareTo(new Pair("c", "b")) );
        assertTrue( 0 == new Pair("b", "d").compareTo(new Pair("d", "b")) );

        assertTrue( 0 == new Pair("c", "c").compareTo(new Pair("c", "c")) );
        assertTrue( 0 == new Pair("c", "d").compareTo(new Pair("d", "c")) );

        assertTrue( 0 == new Pair("d", "d").compareTo(new Pair("d", "d")) );

        assertTrue( 0 > new Pair("a", "a").compareTo(new Pair("b", "b")) );
        assertTrue( 0 > new Pair("a", "a").compareTo(new Pair("c", "b")) );
        assertTrue( 0 < new Pair("d", "d").compareTo(new Pair("c", "b")) );
        assertTrue( 0 < new Pair("c", "d").compareTo(new Pair("c", "a")) );
    }

    @Test
    public void testPairHashcode(){
        assertTrue( 0 != new Pair("a", "a").hashCode());
        assertTrue( 0 != new Pair("a", "b").hashCode());
        assertTrue( 0 != new Pair("a", "c").hashCode());
        assertTrue( 0 != new Pair("a", "d").hashCode());

        assertTrue( 0 != new Pair("b", "b").hashCode());
        assertTrue( 0 != new Pair("b", "c").hashCode());
        assertTrue( 0 != new Pair("b", "d").hashCode());

        assertTrue( 0 != new Pair("c", "c").hashCode());
        assertTrue( 0 != new Pair("c", "d").hashCode());

        assertTrue( 0 != new Pair("d", "d").hashCode());

        assertEquals( new Pair("a", "a").hashCode(), new Pair("a", "a").hashCode() );
        assertEquals( new Pair("a", "b").hashCode(), new Pair("b", "a").hashCode() );
        assertEquals( new Pair("a", "c").hashCode(), new Pair("c", "a").hashCode() );
        assertEquals( new Pair("a", "d").hashCode(), new Pair("d", "a").hashCode() );

        assertEquals( new Pair("b", "b").hashCode(), new Pair("b", "b").hashCode() );
        assertEquals( new Pair("b", "c").hashCode(), new Pair("c", "b").hashCode() );
        assertEquals( new Pair("b", "d").hashCode(), new Pair("d", "b").hashCode() );

        assertEquals( new Pair("c", "c").hashCode(), new Pair("c", "c").hashCode() );
        assertEquals( new Pair("c", "d").hashCode(), new Pair("d", "c").hashCode() );

        assertEquals( new Pair("d", "d").hashCode(), new Pair("d", "d").hashCode() );

        assertNotEquals( new Pair("a", "a").hashCode(), new Pair("b", "b").hashCode() );
        assertNotEquals( new Pair("a", "b").hashCode(), new Pair("b", "d").hashCode() );
        assertNotEquals( new Pair("a", "c").hashCode(), new Pair("d", "a").hashCode() );
        assertNotEquals( new Pair("a", "d").hashCode(), new Pair("a", "a").hashCode() );
    }
}

Но я понял, что, меняя compareTo(), чтобы всегда возвращать 0, каждая пара будет считаться той же, что и результат:

156483558 cb 1000000

в то время как изменение hashCode() всегда возвращает 0 (для тех же входных данных, что указано выше) приведет к тому же результату, что и выше, только с нулевыми клавишами.

0 aa 62822
0 ab 62516
0 ac 62248
0 до 62495
0 ba 62334
0 bb 62232
0 bd 62759
0 ca 62200
0 cb 124966
0 cc 62347
0 dc 125047
0 да 62653
0 дБ 62603
0 дд 62778

Редактировать:

Я исследовал дальше, делая compareTo() печатаем то, что сравнивается. Это показало, что некоторые ключи, такие как a, b и b, a, никогда не сравниваются друг с другом, поэтому не группируются.

Если не все ключи сравниваются друг с другом, то как может быть группировка в любом случае (помимо использования hashCode(), что это не так)?

Я думаю, что есть какая-то крошечная вещь, которую мне не хватает. Я рад за любые идеи! Заранее большое спасибо.

с наилучшими пожеланиями

  • 0
    Просто попробуйте разные входные данные. Может быть, ваши входные данные повреждены.
  • 0
    Входные данные были сгенерированы простым скриптом Python, который случайным образом выбирает две буквы из массива букв [a, b, c, d] (в данном случае) и записывает их в файл. Я не знаю, где они должны были быть испорчены.
Показать ещё 4 комментария
Теги:
hadoop
hadoop2
hadoop-partitioning
mapreduce

3 ответа

0
Лучший ответ

Проблема заключается в функции compareTo(). Сначала проверьте, равны ли они в терминах a, b равно b, a. Если это не так, сначала сравните меньшие значения пар и, если они совпадут, сравните большие значения. пар. Это решает проблему.

Вот как я его реализовал сейчас:

@Override
public int compareTo(Pair other){
    int cmpFirstFirst = first.compareTo(other.first);
    int cmpSecondSecond =  second.compareTo(other.second);
    int cmpFirstSecond = first.compareTo(other.second);
    int cmpSecondFirst =  second.compareTo(other.first);

    if ( cmpFirstFirst == 0 && cmpSecondSecond == 0 || cmpFirstSecond == 0 && cmpSecondFirst == 0) {
        return 0;
    }

    String thisSmaller;
    String otherSmaller;

    String thisBigger;
    String otherBigger;

    if ( this.first.compareTo(this.second) < 0 ) {
        thisSmaller = this.first;
        thisBigger = this.second;
    } else {
        thisSmaller = this.second;
        thisBigger = this.first;
    }

    if ( other.first.compareTo(other.second) < 0 ) {
        otherSmaller = other.first;
        otherBigger = other.second;
    } else {
        otherSmaller = other.second;
        otherBigger = other.first;
    }

    int cmpThisSmallerOtherSmaller = thisSmaller.compareTo(otherSmaller);
    int cmpThisBiggerOtherBigger = thisBigger.compareTo(otherBigger);

    if (cmpThisSmallerOtherSmaller == 0) {
        return cmpThisBiggerOtherBigger;
    } else {
        return cmpThisSmallerOtherSmaller;
    }
}

Это означает, что, в отличие от моего предположения, группировка вывода карты выполняется с использованием транзитивного отношения, а не перекрестного произведения ключей. Необходим стабильный порядок ключей. Это дает полный смысл, как только вы это знаете и понимаете.

1

Учитывая исходные требования, чтобы {a, b} =: = {b, a} было бы нелегко иметь упорядоченные в конструкторе элементы кортежа?

public Pair(String first, String second) {
    boolean swap = first.compareTo(second) > 0;
    this.first = swap ? second : first;
    this.second = swap ? first : second;
}

Это упростит методы, такие как compareTo и equals, а также сделает ненужным выполнение Partitioner.

0

Думаю, я вижу здесь эту проблему. Вы не реализовали разделитель.

Когда вы говорите, что сталкиваетесь с проблемами с большим набором данных, я предполагаю, что вы используете несколько редукторов. Если вы используете один редуктор, код будет работать. Но в случае нескольких редукторов вам нужен разделитель, чтобы сообщить фреймворку, что ab & ba по сути являются одними и теми же ключами и должны перейти к одному и тому же редуктору.

Вот пояснительная ссылка: LINK

  • 0
    На сайте говорится: «Реализация Partitioner по умолчанию называется HashPartitioner. Он использует метод hashCode () ключевых объектов по модулю общего количества разделов, чтобы определить, в какой раздел отправить данную пару (ключ, значение)». Поскольку значения hashCode () совпадают между a, b и b, a, это, вероятно, не проблема.
  • 0
    Хорошая точка зрения. Теперь я невежественен ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню