HBaseCoprocessor是一套通信框架,能够在客户端向RegionServer注入代码并执行获取结果。基于HBaseCoprocessor,可以实现在RegionServer层面的聚合、访问控制及二次索引等功能,从服务端丰富HBase的功能。
HBaseCoprocessor的核心,是类似于Hadoop、HBase的轻量级RPC远程调用和通信框架。这个RPC的基本工作流程是:
1)客户端取得一个服务端通信接口的实例.
2)客户端调用这个实例中的方法
3)客户端向服务端传输调用请求
4)服务端接口实现被调用
5)服务端向客户端传输结果
在Coprocessor架构中,org.apache.org.hadoop.hbase.coprocessor.CoprocessorProtocol是服务端与客户端通信的接口。
客户端 |
服务端 |
通信接口 |
HBase Coprocessor Client |
Region Server |
CoprocessorProtocol |
以源码中基于Coprocessor的Aggregation功能实现为例,说明HBaseCoprocessor的框架。
Coprocessor核心接口与核心类
接口Coprocessor:提供start()和stop()方法,表示Coprocessor的真实执行。
接口CoprocessorProtocol:提供getProtocolVersion()方法,派生接口中可以定义coprocessor需要的功能。
虚拟类BaseEndPointCoprocessor:这是一个默认的EndPoint实现类,它实现了Coprocessor接口和CoprocessorProtocol接口;维护了一个CoprocessorEnvironment内部类来包装HTable和访问Region资源。
类AggregateImplementation:这个类继承BaseEndPointCoprocessor,定义并实现了若干聚合算法,如max、min、avg等方法。这个类将运行在RegionServer上,通过接口Coprocessor的start()、stop()启动和停止,通过聚合算法对本Region中指定Table的数据进行聚合计算。
Coprocessor运行环境类,每一个Coprocessor都运行在Environment中。这个Environment包含了一个HTable的封装类,和一个Coprocessor的实现类;它通过实现接口CoprocessorEnvironment,提供getVersion(),getHBaseVersion, getInstance(), getPriority(), getLoadSequence(),getTable()等功能,配合Coprocessor的启动与停止,维护Coprocessor运行。
接口CoprocessorEnvironment:定义方法getVersion(),getHBaseVersion, getInstance(), getPriority(), getLoadSequence(),getTable()。
虚拟类CoprocessorHost:为HBaseSevices启动和维护运行时的coprocessorinvocation提供一个通用框架。这个类可以载入coprocessor进行运行环境(loadInstance),查询当前的processor(getProcessors)。它为每一个coprocessor,创建一个运行Environment,并纳入管理体系。
静态类CoprocessorHost.Environment:实现CoprocessorEnvironment接口,封装一个HTable和Coprocessor实现类。
类RegionCoprocessorHost:继承自CoprocessorHost,被关联至HRegion中以支持RegionServer上执行coprocessor。它在HRegion的构造函数中初始化。
服务端关键类
类HRegion:HRegion在构造函数中加载RegionCoprocessorHost,即加载已配置的Coprocessor;它建立了一个Map,维护已加载(或者叫registered)的coprocessor的class和handler。其Exec()方法通过已经注册的protocolhandlers,在本地执行CoprocessorProtocol中的方法。这就可以支持客户端的RPC调用。
在初始化时,HRegion会检查加载的Coprocessor是否实现了CoprocessorProtocol接口,如果实现了该接口,则对region数据有对应的处理方法,就将其加入到registeredHandler中,加入内容为(class<T>, T)。
类HRegionServer:暴露客户端远程RPC调用的接口execCoprocessor(regionName,exec)。
客户端关键类
类AggregateClient:通过Coprocessor实现的聚合client函数。通过传入AggregateProtocol类,RPC调用RegionServer上的protocolhandler,完成计算并获得返回值。它实现了min、max、avg等方法。
类Batch:定义了RPC远程执行的接口Call,以及返回接口CallBack。
类HTable:提供RPC远程调用的coprocessorExec方法。
类HConnectionManager:提供RPC远程调用的processExecs方法。
类ExecRPCInvoker:通过hbaseclient中的ServerCallable,RPC远程调用HRegionServer中的暴露execCoprocessor方法,并获得返回值。
以client.coprocessor包中的AggragationClient为例,说明Coprocessor的执行流程
①AggregationClient通过hadoop的conf配置类初始化
②AggregationClient实现了6个聚合方法,max,min,rowcount,sum,avg,std
③各个方法的实现大体一致,我们以一个典型的max为例,说明coprocessor的用法
public<R, S> R max(final byte[] tableName, finalColumnInterpreter<R, S> ci,
finalScan scan) throws Throwable {
validateParameters(scan);//验证参数
HTabletable = new HTable(conf, tableName); //创建htable
classMaxCallBack implements Batch.Callback<R> {//定义callback函数,将运行在regionserver上
Rmax = null;
RgetMax() {
returnmax;
}
@Override
publicsynchronized void update(byte[] region, byte[] row, R result) {
//columnintepreter,compare,比较两个对象的大小,返回正数或者负数值
//columnintepreter,根据T值,作计算。<T,S>,T是cellvalue,S是promoteddata type
max= ci.compare(max, result) < 0 ? result : max;//重写update函数,获得columnfamily中的最大值
}
}
MaxCallBackaMaxCallBack = new MaxCallBack();//创建一个callback对象
//通过htable中增加新的coprocessorExec方法,实现call和callback
//必须实现coprocessorProtocol,这里是AggregateProtocol接口,继承了coprocessorProtocol,定义好了聚合的各种方法
table.coprocessorExec(AggregateProtocol.class,scan.getStartRow(), scan//执行callback,核心类是AggregateProtocol
.getStopRow(),
newBatch.Call<AggregateProtocol, R>() {//call,regionserver上执行的事务
@Override
publicR call(AggregateProtocol instance) throws IOException {
returninstance.getMax(ci, scan); //get max
}
},
aMaxCallBack);//传入back对象
returnaMaxCallBack.getMax();
}
以上方法中的传入参数
-- final byte []tableName:查询的表名称
-- finalColumnIntepreter<R,S> ci:对列值的解释器和计算器,以进行列值计算和比较
-- final Scanscan:定义查询范围,单列值或是多列值,有无起始的rowkey范围,有无timerange等
以上示例中,可以看出,
1.AggregationClient以AggregateProtocol接口类(继承至CoprocessorProtocol)为通信接口,传递给RPC调用方法,Region上获得此AggregateProtocol,在本地调用Handler进行处理,将处理完毕的值返回。看起来,远程调用就像本地执行一样。
2.需要定义region上执行的类Batch.Call,即newBatch.Call<AggregateProtocol, R>() ;
3.需要定义回调函数Batch.CallBack的实现类,即MaxCallBack;
4. 通过client中的HTable的coprocessorExec方法,执行coprocessor,并获得返回值。
org.apache.hadoop.hbase.client.coprocessor.Batch虚拟类,定义了与Coprocessor进行RPC交互的方法和接口。
--静态接口Call<T,R>:T是调用的实例,R是返回值类型。定义了call方法,即针对实例T执行的方法。
--静态接口CallBack<R>:R是返回值类型,定义了update方法;即通过RPC调用获得Region上的coprocessor计算结果后,和现有值进行计算更新。
--静态方法Call<T,R> forMethod(finalClass<T> protocol, final String method, final Object...args):通过protocol、方法名、参数,返回Call接口的实现类引用
②org.apache.hadoop.hbase.client.HConnectionManager.processExec()方法
--由代理类Proxy生成protocol类,并实现相应接口,由invoker调用接口方法。R是计算结果,regionName从invoker获得,callback更新计算值。
-- ExecRPCInvoker-> Exec为参数,HRegionServer(继承HRegionInterface)execCoprocessor函数-> HRegion.exec()
--HRegion.registerProtocol(Class<T> protocol, T handler),注册Region上的protocolHandler;该方法被RegionCoprocessorHost构造函数初始化loadSystemCoprocessors时调用(createEnvironment)。
HConnectionManager.processExec()方法的核心代码
for(final byte[] r : rows) {//针对每一个range的起始行,调用invoke函数
finalExecRPCInvoker invoker =
newExecRPCInvoker(conf, this, protocol, tableName, r); //建立一个rpc通道
Future<R>future = pool.submit(//提交任务,获得future
newCallable<R>() {
publicR call() throws Exception {
Tinstance = (T)Proxy.newProxyInstance(conf.getClassLoader(),
newClass[]{protocol},
invoker); //创建一个coprocessorProtocol接口的本地proxy,以远程调用region上的coprocessor,获得返回值
Rresult = callable.call(instance); //通过rpc通道,proxy的invoker远程调用call指定的方法(min、max等),传递返回值给R
byte[]region = invoker.getRegionName(); //invoker.call方法已经为invoker的regionName赋值;这个方法直接返回regionName
if(callback != null) {
callback.update(region,r, result); //callback的update方法,对rpc计算获得值和原有值作聚合计算(min、max等),更新结果值
}
returnresult;
}
});
futures.put(r,future);
}
ExecRPCInvoker的invoke核心代码
publicObject invoke(Object instance, final Method method, final Object[]args)
throwsThrowable {
if(LOG.isDebugEnabled()) {
LOG.debug("Call:"+method.getName()+", "+(args != null ?args.length : 0));
}
if(row != null) {
finalExec exec = new Exec(conf, row, protocol, method, args);
ServerCallable<ExecResult>callable =
newServerCallable<ExecResult>(connection, table, row) {
publicExecResult call() throws Exception {
returnserver.execCoprocessor(location.getRegionInfo().getRegionName(),
exec); //通过RPC,调用HRegionServer上暴露的方法
}
};
ExecResultresult = connection.getRegionServerWithRetries(callable);
this.regionName= result.getRegionName();
LOG.debug("Resultis region="+ Bytes.toStringBinary(regionName) +
",value="+result.getValue());
returnresult.getValue();
}
HRegion的exec核心代码
CoprocessorProtocolhandler = protocolHandlers.getInstance(protocol); //protocolHandlers在RegionCoprocessorHost初始化时载入,即预设的coprocessor集合
Objectvalue;
Class<?>returnType;
try{
Methodmethod = protocol.getMethod(
call.getMethodName(),call.getParameterClasses());
method.setAccessible(true);
returnType= method.getReturnType();
value= method.invoke(handler, call.getParameters()); //HRegion上的本地接口执行方法,这是实际执行的地方
}
后续重要的是coprocessor对hlog的分析