Mapreduce实例-分组排重(group by distinct)

本文介绍了一个使用Hadoop MapReduce进行排重的案例,通过自定义的比较器、分区器等组件实现了对大数据集的有效处理。文章详细解释了如何通过MapReduce的各个环节来确保相同的数据被正确地归类并进行计数。

需要实现以下几个类,代码太多,列了下主要代码,可根据排重数据的特征判读是否需要添加combiner来提速。

public class GroupComparator implements RawComparator<MyBinaryKey> {
 
 @Override
 public int compare(MyBinaryKey o1, MyBinaryKey o2) {
  return o1.toString().compareTo(o2.toString());
 }

 @Override
 public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
  return WritableComparator.compareBytes(b1, s1, Long.SIZE / 8 + Integer.SIZE / 8 * 3, b2, s2,  Long.SIZE / 8 + Integer.SIZE / 8 * 3);
 }

}

public abstract class UVBinaryKey  extends BinaryComparable implements WritableComparable<BinaryComparable>{
 //根据需要添加属性;
  @Override
 public void readFields(DataInput in) throws IOException {

} 

@Override
 public byte[] getBytes() {

}

}

public class MyPartitioner extends Partitioner<MyBinaryKey, NullWritable> {

 /**
  * 根据uv/ip取模分区,保证相同uv/ip落在同一分区
  */
 @Override
 public int getPartition(MyBinaryKey key, NullWritable value, int numPartitions) {
  
  int k=0;
  for(byte b : key.getAttr()){
   k+=b&0xff;
  }
  return k%numPartitions;
 }

}



  job.setMapOutputKeyClass(UVBinaryKey.class);
  job.setGroupingComparatorClass(GroupComparator.class);
   job.setPartitionerClass(MyPartitioner.class);

map略
combiner(根据需要添加)
reduce中的实现:
       @Override
        protected void reduce(UVBinaryKey key, Iterable<NullWritable> values, Context context)
                throws IOException,
                InterruptedException {
            long count = 0;
            byte[] tbsign = null;
            for (NullWritable nullWritable : values) {
                byte[] attr = key.getAttr();
                if (tbsign == null) {
                    tbsign = attr;
                    count++;
                }
                if (tbsign != null) {
                    if (tbsign.length != attr.length) {
                        count++;
                        tbsign = attr;
                    } else {
                        for (int i = 0; i < tbsign.length; i++) {
                            if (tbsign[i] != attr[i]) {
                                count++;
                                tbsign = attr;
                                break;
                            }
                        }
                    }
                }

            }
            StringBuffer out = new StringBuffer();
            out.append(new String(key.getCity()))
                    .append(Constants.FIELDS_TERMINATED).append(count);
            context.write(new Text(out.toString()), NullWritable.get());

        }


### Hive 中 `DISTINCT` 导致的数据倾斜 当在Hive查询中使用`SELECT DISTINCT`操作时,可能会遇到数据倾斜问题。这是因为`DISTINCT`操作本质上会触发一个全局聚合过程,所有的记录都需要被收集并传递给单个Reducer来进行唯一化处理[^1]。 #### 原因分析 - **单一Reducer负担过**:默认情况下,`DISTINCT`会使所有待的键值都发送到同一个Reducer实例执行,这可能导致该Reducer承载过多的任务负载,形成瓶颈。 - **网络传输压力增大**:如果原始表中的某列存在大量复值,则这些相同的值会被映射至同一组Key下,并通过网络传送给指定的Reducer节点,增加了不必要的通信开销[^4]。 - **内存溢出风险**:对于非常庞大的数据集来说,单个Reducer尝试一次性加载全部不同Keys入内存进行比较和过滤很容易超出JVM堆空间限制,进而引发OOM(Out Of Memory)异常[^5]。 ```sql -- 这是一个典型的会造成倾斜的distinct语句 SELECT DISTINCT column_name FROM table; ``` 为了缓解上述情况带来的性能挑战,下面介绍几种有效的应对策略: #### 解决方案 ##### 方法一:增加Mapper数量与预聚合 可以通过设置参数调整Map阶段的工作线程数目以及开启局部聚合功能,让部分工作提前发生在多个Mappers内部而不是集中于最后一步Reduce环节完成。这样做的好处是可以显著减轻最终汇聚点的压力。 ```properties set hive.groupby.skewindata=true; -- 开启skewed data优化选项 set mapreduce.job.reduces=200; -- 手动设定适当规模reducer个数 ``` ##### 方法二:引入随机因子打散热点 针对特定字段上的高度聚集现象,可以在原有基础上附加一个小范围内的伪随机数值作为辅助Hash Key参与Shuffle序流程,以此达到分散目的地址的效果。之后再利用额外的一轮Join操作恢复原本意图获取的结果集合。 ```sql WITH temp AS ( SELECT *, ABS(FLOOR(RAND() * N)) as rand_key FROM source_table ) DISTRIBUTED BY (rand_key); ``` 此处N表示期望划分成多少份独立子集;RAND函数用于生成介于0~1之间的浮点型随机数;ABS确保绝对正值输出;FLOOR向下取整以便适配后续逻辑运算需求。 ##### 方法三:采用Bitmap索引或布隆过滤器替代传统哈希算法 这两种高级结构能够以极低的空间复杂度实现高效的成员资格测试,特别适用于那些仅需判断是否存在而不关心具体位置的应用场景。它们可以帮助我们快速定位哪些条目已经出现过了,从而跳过冗余计算步骤。 ```java // 使用Java代码片段展示如何创建Bloom Filter对象 import org.apache.hadoop.util.bloom.BloomFilter; public class BloomExample { public static void main(String[] args){ int numItems = ... ; // 预估元素总数 double fpp = 0.01; // 可接受的最大误报率 BloomFilter bloomFilter = new BloomFilter(numItems,fpp); // 向filter添加项目... String itemToAdd = "example"; bloomFilter.add(itemToAdd.getBytes()); // 查询某个item是否存在于bloom filter中... boolean mightContain = bloomFilter.membershipTest("test".getBytes()); } } ``` 以上三种方式各有优劣,在实际应用当中可根据具体情况灵活选用最合适的手段来克服由`DISTINCT`引起的潜在倾斜难题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值