hbase coprocessor 应用实践

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

猜你喜欢

转载自xiangjinqi.iteye.com/blog/2119630