应用场景,在很多情况下我们只希望复杂的逻辑来过滤数据,得到的数据可能只有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();