应用场景,在很多情况下我们只希望复杂的逻辑来过滤数据,得到的数据可能只有1M,但是数据源可能会达到1T,譬如需要知道对iphone比较感兴趣的用户有哪些。
需要过滤里面的字段品牌和相应的权重,
如果全部将数据读入mapreduce意味着较多的IO开销。
下面附上本人的代码
JobTask jobTask = new JobTask(null, new Path("/user/pms/xq/full_user_profile1/" + i))
.setInputFormat(TableInputFormat.class)
.setMapper(BrandTopMapper.class)
.setMapperKey(Text.class).setMapperValue(NullWritable.class)
.setReducer(null)
.setOutputFormat(TextOutputFormat.class)
.setJobOptionsSetter(new JobOptionsSetter() {
@Override
public void setOptions(Job job) throws Exception {
Configuration conf = job.getConfiguration();
conf.set(DataImpConstants.MAPRED_REDUCE_TASKS,
getOption(MAPRED_REDUCE_TASKS));
conf.set("mapred.job.queue.name", "pms");
Scan scan = new Scan();
scan.setCaching(1000);
scan.setCacheBlocks(false);
scan.setStartRow(pair.getFirst());
scan.setStopRow(pair.getSecond());
scan.setId("com.yhd.db.hbase.job.coprocessors.BrandFilter");
HbaseUtils.createRegionScan(job, scan);
conf.set(TableInputFormat.INPUT_TABLE,
getOption(TABLE_NAME));
conf.set(FAMILY_NAME, getOption(FAMILY_NAME));
}
});
scan.setStartRow(pair.getFirst());
scan.setStopRow(pair.getSecond());
这两行是分批扫描整张表。
其中com.yhd.db.hbase.job.coprocessors.BrandFilter 是传到hbase region服务器,让服务器决定用哪一个过滤器
public class HGetBase extends BaseRegionObserver {
ObserverFilter observerFilter;
@Override
public boolean postScannerNext(
ObserverContext<RegionCoprocessorEnvironment> e, InternalScanner s,
List<Result> results, int limit, boolean hasMore)
throws IOException {
if(observerFilter != null) {
for(int i=results.size() - 1; i >=0; i--) {
if(!observerFilter.execute(results.get(i))) {
results.remove(i);
}
}
}
return super.postScannerNext(e, s, results, limit, hasMore);
}
@Override
public RegionScanner postScannerOpen(
ObserverContext<RegionCoprocessorEnvironment> e, Scan scan,
RegionScanner s) throws IOException {
if(scan.getId() != null) {
try {
observerFilter = (ObserverFilter) Class.forName(scan.getId()).newInstance();
} catch (Exception e1) {
e1.printStackTrace();
}
}
return super.postScannerOpen(e, scan, s);
}
}
这个就设置为coprocessor的类
譬如
disable 'top_user_profile'
alter 'top_user_profile', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
alter 'top_user_profile', METHOD => 'table_att', 'coprocessor'=>'hdfs:///user/pms/xq/protest13.jar|com.yhd.db.hbase.job.coprocessors.HGetBase|1000'
enable 'top_user_profile'
根据前面的参数com.yhd.db.hbase.job.coprocessors.BrandFilter,会执行下面这个过滤类
public interface ObserverFilter {
public boolean execute(Result result);
}
扩展性的接口
public class BrandFilter implements ObserverFilter {
@Override
public boolean execute(Result result) {
for(Entry<byte[], byte[]> r : result.getFamilyMap("cat".getBytes()).entrySet()) {
UserCateProfile uc = JSON.parseObject(new String(r.getValue()),
UserCateProfile.class);
List<UserAttriProfile> list = uc.getUserAttriProfiles();
for(UserAttriProfile ua : list) {
if(ua.getAttributeType() == 0) {
//brand权重和名字的判断 907647 苹果
Set<AttributeItem> items = ua.getItems();
for(AttributeItem ai : items) {
//中兴 936432 苹果929029
if(ai.getId() == 929029 && ai.getAv() > 0.3) {
return true;
}
}
}
}
}
return false;
}
}
region server会执行相应的过滤代码,大大的减小了IO开销,缩短了执行时间。
注意的问题就是,如果要更新jar包,可能存在不支持覆盖的,jar被从hdfs上load过去在本地缓存了在临时文件了,region server还是在用临时文件,没有做覆盖操作。需要更改jar的名字或路径,才能让新的包生效,
缺点是反复更改会造成磁盘空间不足。
源代码
- CoprocessorClassLoader
缓存了jar的class loader信息,
private static final ConcurrentMap<Path, CoprocessorClassLoader> classLoadersCache =
new MapMaker().concurrencyLevel(3).weakValues().makeMap();