本文基于hbase-1.3.0源码
1. 前言
HBase coprocessor(协处理器)按照工作方式分为两种:endpoint和observer。
- endpoint
类似mysql数据库中的存储过程,用户继承接口编写endpoint,由hbase加载。加载完成后可以由客户端rpc调用。 - observer
observer类似mysql中的触发器,同样由用户继承接口编写observer,由hbase加载。observer还可以细分为4类,下文会细讲。observer不需要客户端通过rpc去调用,observer满足条件时触发。
由于这两类之间没有什么关联,下文将按照这两类分章节讲述。
2. Observer
2.1 observer分类
observer可以划分为四类,通过实现下面4个接口来自定义4类obserser:
- RegionServerObserver
此类observer应该由region server加载,并在region server上发生操作是触发 - RegionObserver
此类observer应该由region创建时加载,并在region上发生操作时触发 - MasterObserver
此类observer应该由master创建时加载,在master上发生操作时触发 - WALObserver
此类observer是在创建wal(write ahead log)时加载,在wal发生相关操作时触发。
以上4类接口定义的一些接口方法,实际上作为一种钩子方法,会在涉及regionserver/region/master/wal不同操作时由hbase调用。比如RegionObserver中preAppend
和postAppend
这种会在对region做append操作完成前后回调 。自定义observer实现这些接口方法,由加载observer的执行环境根据当前操作决定调用observer的哪种方法。
由于每一类observer都可以定义任意个数,理论上每一个observer都会触发。
2.2 执行环境
上文提到不同的observer会在不同的地方加载。针对每一类observer,Hbase都会创建一个叫 XXXCoprocessorHost
的实例,名字带Host说明它是提供加载和运行环境的,它主要负责以下工作:
- 负责加载并管理相应类型的observer,比如RegionCoprocessorHost只加载所有的RegionObserver。
- 并在特定操作发生时,hbase是通过对应的
XXXCoprocessorHost
触发其加载的所有observer的对应方法,。
比如region上发生append操作时,调用RegionCoprocessorHost # preAppend
在append之前触发所有加载的RegionObserver 的preAppend方法。
和4种observer中类对应的 XXXCoprocessorHost
同样分为4类:
- RegionCoprocessorHost
- RegionServerCoprocessorHost
- MasterCoprocessorHost
- WALCoprocessorHost
下面先说说这四类CoprocessorHost吧。
由于它们都继承自Coprocessor
这个抽象类,需要先解释以下这个类:
1. CoprocessorHost
这是这个类的声明:
public abstract class CoprocessorHost<E extends CoprocessorEnvironment>
{...}
-------------------
他有如下成员:
1. protected SortedList<E> coprocessors =
new SortedList<E>(new EnvironmentPriorityComparator());
coprocessor保存了当前Coprocessor,类型参数E 是CoprocessorEnvironment的子类,这里是对Coprocessor做了以下封装,
CoprocessorEnvionment中还保存了一些Coprocessor执行的必要环境,后文会讲到。
2. protected void loadSystemCoprocessors(Configuration conf, String confKey)
XXXCoprocessorHost创建时,会根据配置文件默认加载的coprocessor,由配置文件中的confKey这个配置项决定,不同的CoprocessorHost这个confKey时不同的,讲到CoprocessorHost时会提到。
3. public E load(Path path, String className, int priority,
Configuration conf)
这个方法是用来从hdfs上加载类的,path是hdfs上存放的coprocessor所在的jar包, className即要加载的类。
4. public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
int priority, int sequence, Configuration conf)
这是抽象方法,具体的XXXCoprocessorHost需要实现, load加载并创建出coprocessor实例,委托这个方法将coprocessor包装到特定的enviroment中。
2. Environment
用户只需要要着眼于实现Coprocessor,但是XXXCoprocessorHost加载coprocessor后会包装成Environment,它是一个公共基类,XXXCoprocessorHost中都会继承它实现一个特定的enviornment,并在里面包装一些自己的信息。
提供Environment的意义在于:
有时后需要在自定义的coprocessor中访问hbase中的table或者访问hmaster或者hregionserver的一些服务,访问region信息等等,Enviornment中提供了这个便利。
enviroment还提供了在不同同一种CoprocessorHost加载的不同coprocessor中共享数据的方式,也就是通过Enviroment
每一个coprocessor方法的第一个参数都如同下面那样:
void xxx(final ObserverContext<XXXCoprocessorEnvironment> c, ...) throws IOException;
CoprocessorHost在调用coprocessor方法时,会把自己的enviornment注入到这个参数上,自定义的coprocessor可以调用。
不同的CoprocessorHost会使用不同的Enviornment,如下:
- RegionEnvironment: RegionCoprocessorHost创建
- MasterEnviornment:MasterCoprocessorHost创建
- RegionServerEnvionment: RegionServerCoprocessorHost创建
- WALEnviornment: WALCoprocessorHost创建
2.2.1 RegionCoprocessorHost
RegionCoprocessorHost在创建HRegion时创建,它管理所有RegionObserver
的实现类,并调用它们的钩子方法。
1. 加载的Coprocessor
- 由配置
hbase.coprocessor.region.classes
加载指定的coprocessor; - 此外,如果当前Region上的表为非系统表(即不是'hbase'这个namespace下的表),那么它还会加载配置
hbase.coprocessor.user.region.classes
对应的class。 - 此外,还会加载通过表属性字段设置的一些coprocessor,属性名必须是:coprocessor-{数字}(说明可以是多个coprocessor), 属性值符合:"url_to_your_jar|coprocessor_classname|priority_no|arg1=value1,arg2=value2,...,argN=valueN"这种格式,下面是一个hbase中的例子:
hdfs:///foo.jar|com.foo.FooRegionObserver|1001|arg1=1,arg2=2 jar包在hdfs上,加载coprocessor为“ com.foo.FooRegionObserver”,优先级1001(越低越优先调用), 后面的参数被设置到Configuration中,Configuration又被封装到Environment中。
附:
RegionCoprocessorHost还会加载Endpoint Service,如果你的Coprocessor实现了接口CoprocessorService
,实现了接口的唯一方法Service getService();
。那么还会注册这个service到region上,然后就可以通过rpc去调用这个endpoint service。这个会在后面Endpoint一节中讲述。
2. coprocessor的执行
这里从源码介绍一下RegionCoprocessorHost是怎么执行Coprocessor的。coprocessor的钩子方法是通过RegionCoprocessorHost的一些对等的方法调用的,以RegionCoprocessorHost的下面两个方法为例:
class RegionCoprocessorHost{
...
public InternalScanner preFlush(final Store store, final InternalScanner scanner)
throws IOException {
return execOperationWithResult(false, scanner,
coprocessors.isEmpty() ? null : new RegionOperationWithResult<InternalScanner>() {
@Override
public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
throws IOException {
setResult(oserver.preFlush(ctx, store, getResult()));
}
});
}
/**
* Invoked before a memstore flush
* @throws IOException
*/
public void preFlush() throws IOException {
execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
@Override
public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
throws IOException {
oserver.preFlush(ctx);
}
});
}
...
}
----------------
1. 这两个方法都是在Memstore flush到磁盘前调用的,这两个方法会调用RegionCoprocessorHost加载的所有的RegionCoprocessor # preFlush方法。
2. 有个重要的区别是前者有返回值,后者没有。有返回值的Coprocessor方法意味着可以用返回值替换输入,比如上面第一个方法输入参数scanner可以被替换为返回值,编写coprocessor的人应该很清楚自己在干什么。
3. 这么多coprocessor,是按什么顺序执行的?按优先级,Environment 创建时都会指定优先级,通过getPriority()返回.
上面「1. 加载的Coprocessor」中那个表属性设置加载的coprocessor中的例子有优先级‘1001’,数字越小,优先级越高。系统加载的优先级都比较低(Integer.MAX_VALUE / 4)。
由于其他几类CoprocessorHost执行coprocessor的方式差不多,后面就不再介绍了。
3. RegionEnviornment
所有的RegionObserver的方法第一个参数都是ObserverContext的子类,从上面的代码可以看出这个context是RegionCoprocessorHost注入的,ObserverContext主要的成员就是Environment的对象,各类CoprocessorHost都实现了Environment,在里面包装了不同的信息供coprocessor使用。
下面看看可以使用RegionEnviornement干什么:
RegionEnviornment的主要成员:
1. private Region region;
加载当前RegionCoprocessorHost的Region
2. private RegionServerServices rsServices;
用于访问RegionServer
3. ConcurrentMap<String, Object> sharedData;
sharedData用来在同一个coprocessor的多次调用之间共享数据
主要方法:
1. public Configuration getConfiguration()
获取hbase配置信息
2. HTableInterface getTable(TableName tableName)
从表名获取访问以及操作表的接口。
2.2.2 RegionServerCoprocessorHost
它在RegionServer启动过程中创建,因此一个RegionServer只有一个。RegionCoprocessorHost都是涉及到当前的region的一些操作时(put,get,split等)会回调,RegionServerCoprocessorHost显然涉及的操作都是RegionServer范围可见的(比如多个region的merge,基于wal的一些操作,wal时region server范围内维持的,等等)。
1. 加载的coprocessor
加载由配置项hbase.coprocessor.regionserver.classes
决定的coprocessor。要想coprocessor在上运行,需要实现接口RegionServerObserver
。
附:
如果你的coprocessor实现了SingletonCoprocessorService
接口,那么RegionServerCoprocessorHost会调用它的唯一接口方法‘getService()’返回一个endpoint service,并注册这个endpoint,然后就可以通过客户端调用了。
2. RegionServerEnvironment
比较简单,只有一个成员如下:
private RegionServerServices regionServerServices;
访问region server的一些服务
2.2.3 MasterCoprocessorHost
在HMaster启动过程中创建, master负责的操作很多(修改表,region 相关操作,procedure相关的)。
1. 加载的coprocessor
由配置项hbase.coprocessor.master.classes
决定, MasterCoprocessorHost上运行的coprocessor需要实现‘MasterObserver’接口。
附:
如果coprocessor实现了接口CoprocessorService
,那么MasterCoprocessorHost会调用这个接口的唯一方法"getService"获得endpoint,然后注册它。
2. MasterEnvironment
成员:
private MasterServices masterServices;
访问master的一些服务