Hadoop二次排序

最近在特征开发的过程中,遇到这样的场景,A文件中存储着一些属性(例如文档的ctr),需要将这些属性set到B文件中的文档中,在Hadoop程序设计中,一般是在map中读入文件,然后输出以文档id为key,进入reduce后,先循环遍历value,找到A中的属性值,把B中的文档集记录在List中,然后再循环这个List,把A的属性值set进去,示例代码如下

mapper中
if(from()=="Afile"){
    context.write(new Text(docid), new Text(ctr));
}else {
    context.write(new Text(docid), new Text(docEntity.toString()));
}

reducer中
double ctr = defaultCtr;
List<String> docList = new ArrayList();
for(Text val: values){
    String vl = val.toString();
    if(fromA()==true){
        ctr = Double.parseDouble(vl);
    }else{
        docList.add(vl);
    }
}
for(String doc: docList){
    DocEntity docEntity = DocEntity.parse(doc);
    docEntity.setCtr(ctr);
}

这样做,相当于额外增加了一次循环,而且当文档数较多的时候,存储在List会造成额外的内存负担。同时注意,reduce中的迭代器values是不能再次迭代的。
我们可以重载Hadoop的二次排序方法,使得进入reduce后,ctr能够必然的排在doc文档之前,这样在一次迭代中就可以直接设置ctr值。首先我们梳理下Hadoop的分区排序过程。
(1)map使用job.setPartitionerClass对key进行分区,决定item进入哪一个reducer。然后调用job.setSortComparatorClass对key进行排序,如果没有通过job.setSortComparatorClass设置key比较函数类,则使用key的实现的compareTo方法;
(2)reducer接收到map的输出后,同样调用job.setSortComparatorClass设置的key比较函数类对所有item排序。然后使用job.setGroupingComparatorClass设置的分组函数类对item分组。只要这个比较器认为两个key相同,就属于同一个group,它们的value放在一个value迭代器中。
从上面可以看出,我们需要重载3个类,自定义Partition,Comparator,GroupingComparator,就可以自己控制排序过程,排出我们想要的次序。下面给出示例代码

自定义partition(分区时,只使用原始的key,去掉排序域)
public class SanilPartition extends Partitioner<Text,Text>{
    private static final Logger logger = LoggerFactory.getLogger(SanilPartition.class); 
    @Override
    public int getPartition(Text key, Text value, int numPartitions) {
        String keyStr = ((Text)key).toString();
        String[] keyArray = keyStr.split("\001");
        String realkey = keyArray[0];
        Text rText = new Text(realkey);
        return  (rText .hashCode()&Integer.MAX_VALUE)%numPartitions;
    }
}
自定义comparator(带上排序域,排出我们想要的次序)
public class SnailComparator extends WritableComparator {
    private static final Logger logger = LoggerFactory.getLogger(Text.class);

    public SnailComparator() {
        super(Text.class, true);
    }
    @Override
    public int compare(WritableComparable one,  WritableComparable two) { 
        String oneStr = ((Text)one).toString();
        String twoStr = ((Text)two).toString();
        return oneStr.compareTo(twoStr);
    }
}
自定义group(分组时,只使用原始的key)
public class SnailGroup extends WritableComparator {
    private static final Logger logger = LoggerFactory.getLogger(Text.class);

    public SnailGroup() {
        super(Text.class, true);
    }
    @Override
    public int compare(WritableComparable one,  WritableComparable two) { 
        String oneStr = ((Text)one).toString();
        String[] oneArray = oneStr.split("\001");
        String onekey = oneArray[0];
        String twoStr = ((Text)two).toString();
        String[] twoArray = twoStr.split("\001");
        String twokey = twoArray[0];
        return onekey.compareTo(twokey);
    }
}
在main函数中设置
job.setPartitionerClass(SanilPartition.class);
job.setSortComparatorClass(SnailComparator.class);
job.setGroupingComparatorClass(SnailGroup.class);

上面的定义中,实际是在原有的key中增加了一个域,与原始的key用\001分隔,由增加的这个域控制排序次序,分区和分组还是使用原始的key。现在的mapper需要改成

if(from()=="Afile"){
    //增加排序域A,使其排序在最前
    context.write(new Text(docid+"\001A"), new Text(ctr));
else {
    //增加排序域B,使其排序在其次
    context.write(new Text(docid+"\001B"), new Text(docEntity.toString());
}

这样,我们就能控制ctr进入reduce后肯定会排在前面,示例代码

double ctr = defaultCtr;
List<String> docList = new ArrayList();
for(Text val: values){
    String vl = val.toString();
    if(fromA()==true){
        ctr = Double.parseDouble(vl);
    }else{
        DocEntity docEntity = DocEntity.parse(vl);
        docEntity.setCtr(ctr);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值