HBase轻量级框架Parrot
目录
一、Parrot是什么?
二、Parrot的数据源配置与连接
三、使用无状态的实体Bean执行CRUD;
四、Parrot所支持的原生HBase特性;
五、Parrot中被废弃的事物操作;
六、Parrot的设计实现;
一、Parrot是什么?
在弄清楚Parrot是什么之前,你需要首先弄清楚HBase是什么,HBase能够适用于哪些场景?当这一切你都明白后,再开看看如何使用Parrot。
Parrot是鹦鹉的单词,或许源于家族遗传因素,本人对鸟类一直比较感冒,所以故用此名作为该项目的名字。Parrot是一种超轻量级的HBase框架,它对代码的侵入极低,且不依赖于任何容器,也就是说最简单的Java工程中也能够跑起来。就目前而言,市面上几乎没有成熟的HBase框架,那么Parrot诞生的目的已经很明确了,就是为了开发实惠!
目前开发人员在程序中大部分都是直接使用HBase Client API操作HBase。笔者使用该工具在项目中倒腾了差不多有半年多的时间,不得不说,使用原生的HBase Client操作HBase确实是一件非常痛苦的事情,不仅开发耗时,且代码冗余度极高。所以我在HBase Client API的基础之上,轻量级的封装了一层,以达到极致简化的目的,这便是Parrot的重。Parrot目前的最新版本是1.1,后续版本中,Parrot将会继续迭代。
Parrot的优点如下:
□ 降低原本90%的工作量,使你只需关注于业务;
□ 使用无状态的Entity Bean方式,对HBase进行CRUD操作;
□ 低侵入式设计,与业务耦合极低;
□ 支持从Diamond中进行数据源信息的配置和加载;
□ 数据检索的自动映射操作;
□ 支持HBase原生的TTableInterface接口;
Parrot缺省依赖Diamond、HBase Client API等相关构件!
二、Parrot的数据源配置与连接
Parrot不支持HBase缺省的基于配置文件的方式配置数据源信息,之所以这么做是因为在大规模的项目场景中,资源配置最好是集中式的,以此避免每一次的项目部署都需要修改集群节点中的配置文件,所以Parrot选用了淘宝的Diamond最为集中式配置中心。
Parrot在Diamond中的数据源配置信息(dataId=hbase_info,组名=DEFAULT_GROUP):
rootdir=hdfs://CNSZ141222:30000/hbase
distributed=true
quorum=CNSZ141222,CNSZ141223,CNSZ141224
maxSize=1000
其中rootdir属性配置了HDFS的地址,distributed属性配置了HBase是否集群,quorum属性配置了ZK的与IP地址对应的机器名,最后一个属性maxSize则用于配置TablePool的连接数。
当你第一次使用Parrot的时候,你需要现将上述信息配置在Diamond中。然后在程序中你只需通过如下语句,便可以建立与HBase的连接:
publicclass ParrotTest {
privatestatic ConnectionConfig conn;
@BeforeClass
publicstaticvoid testConnection() {
conn = new ParrotConnectionConfig();
}
}
}
ConnectionConfig接口是Parrot的数据源接口,支持从Diamond集中式配置中心获取HBase的数据源信息并建立与HBase的会话连接。前面我们说过Parrot能够及时响应Diamond的变化,一旦Diamond中的配置信息发生变化,Parrot便会重新调用ConnectionConfig接口中的loadDataSource()方法,重新初始化数据源信息并关闭内部打开的TablePool连接。
ConnectionConfig接口的常用方法如下:
□
MetaDataTablegetTable()
:获取一个用于操作HBase元数据的Table对象.
□
ParrotTablegetTable(java.lang.Class entityClass)
:根据实体Bean中的信息从HBase中获取指定的Table对象;
□ ParrotTemplategetTemplate()
:获取一个用于执行数据检索的ParrotTemplate对象;
□ void
loadDataSource(com.taobao.diamond.manager.ManagerListener listener)
:从Diamond集中式配置中心获取出HBase的数据源信息;
三、使用无状态的实体Bean执行CRUD;
使用无状态的实体Bean操作HBase,翻译白话点,就是使用对象的方式操作HBase。光从名字就能够听出来,既然使用了面向对象特性,那么操作HBase必然比使用原生的HBase Client API更简单,不过Parrot不仅支持实体Bean的方式对HBase执行CRUD操作,并且对于数据检索,Parrot还能够做到将检索的数据结果集,自动映射到实体Bean上。
在使用实体Bean操作HBase之前,实体Bean必须添加@Entity注解,如下所示:
@Entity(tableName = "my_table", cloumnFamily = { "info" })
上述住接种,tableName属性指定了HBase的表名,cloumnFamily则指定了列族,很遗憾的是目前Parrot1.0的版本中,只能够暂时支持一个列族。不过这并不我们的使用,且按常理说,列族的数量越少,HBase的执行效率越快,这也是实时。
定义实体Bean对象:
@Entity(tableName = "my_table", cloumnFamily = { "info" })
publicclass Bean1 {
private String userName, passWord;
public String getUserName() {
returnuserName;
}
publicvoid setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
returnpassWord;
}
publicvoid setPassWord(String passWord) {
this.passWord = passWord;
}
}
使用Parrot操作实体Bean对象执行CRUD操作:
privatestatic ConnectionConfig conn;
@BeforeClass
publicstaticvoid testConnection() {
conn = new ParrotConnectionConfig();
}
@Test
publicvoid testInsert() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
Bean1 entityObj = new Bean1();
entityObj.setUserName("JohnGao");
entityObj.setPassWord("123456");
table.insert(entityObj, "key1");
}
@Test
publicvoid testInserts() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
Bean1 entityObj1 = new Bean1();
entityObj1.setUserName("JohnGao1");
entityObj1.setPassWord("123456");
Bean1 entityObj2 = new Bean1();
entityObj2.setUserName("JohnGao2");
entityObj2.setPassWord("123456");
List<Bean1> entityObjs = new ArrayList<Bean1>();
entityObjs.add(entityObj1);
entityObjs.add(entityObj2);
List<String> rowKeys = new ArrayList<String>();
rowKeys.add("key1");
rowKeys.add("key2");
table.insert(entityObjs, rowKeys);
}
@Test
publicvoid testUpdate() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
Bean1 entityObj = new Bean1();
entityObj.setUserName("JohnGao1");
entityObj.setPassWord("1111111");
table.update(entityObj, "key1");
}
@Test
publicvoid testUpdates() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
Bean1 entityObj1 = new Bean1();
entityObj1.setUserName("JohnGao1");
entityObj1.setPassWord("2222222");
Bean1 entityObj2 = new Bean1();
entityObj2.setUserName("JohnGao2");
entityObj2.setPassWord("33333333");
List<Bean1> entityObjs = new ArrayList<Bean1>();
entityObjs.add(entityObj1);
entityObjs.add(entityObj2);
List<String> rowKeys = new ArrayList<String>();
rowKeys.add("key1");
rowKeys.add("key2");
table.update(entityObjs, rowKeys);
}
@Test
publicvoid testDelete() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
table.delete("key1");
}
@Test
publicvoid testDeletes() throws Exception {
ParrotTable table = conn.getTable(Bean1.class);
List<String> rowKeys = new ArrayList<String>();
rowKeys.add("key1");
rowKeys.add("key2");
table.delete(rowKeys);
}
@Test
publicvoid testGet1() throws Exception {
ParrotTemplate template = conn.getTemplate();
Bean1 bean = (Bean1) template.get(Bean1.class, "key1");
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
@Test
publicvoid testGet2() throws Exception {
ParrotTemplate template = conn.getTemplate();
List<String> rowKeys = new ArrayList<String>();
rowKeys.add("key1");
rowKeys.add("key2");
List<Bean1> beans = (List<Bean1>) template.get(Bean1.class, rowKeys);
for (Bean1 bean : beans) {
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
@Test
publicvoid testGet3() throws Exception {
ParrotTemplate template = conn.getTemplate();
Object[] objs = template.get(Bean1.class,
new String[] { "key1", "key2" });
for (Object obj : objs) {
Bean1 bean = (Bean1) obj;
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
@Test
publicvoid testfind1() throws Exception {
ParrotTemplate template = conn.getTemplate();
List<Bean1> beans = template.find(Bean1.class, "key", null, null);
for (Bean1 bean : beans) {
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
@Test
publicvoid testfind2() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParrotTemplate template = conn.getTemplate();
List<Bean1> beans = template.find(Bean1.class, "key",
format.parse("2014-05-23 00:00:00").getTime(),
format.parse("2014-05-24 00:00:00").getTime());
for (Bean1 bean : beans) {
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
@Test
publicvoid testfind3() throws Exception {
ParrotTemplate template = conn.getTemplate();
List<Bean1> beans = template
.find(Bean1.class, "key", null, null, 0, 10);
for (Bean1 bean : beans) {
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
@Test
publicvoid testfind4() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParrotTemplate template = conn.getTemplate();
List<Bean1> beans = template.find(Bean1.class, "key",
format.parse("2014-05-22 00:00:00").getTime(),
format.parse("2014-05-23 00:00:00").getTime(), 0, 10);
for (Bean1 bean : beans) {
System.out.println(bean.getUserName());
System.out.println(bean.getPassWord());
}
}
上述程序中我们很好的使用了Parrot的对象特性对HBase进行了CRUD操作,那么ConnectionConfig中包含2个getTable()方法,上述程序中使用getTable()方法成功的获取到了一个ParrotTable对象,该对象代表着可以用于操作HBase Table的一个表对象。
如果需要执行查询,我们则可以使用ConnectionConfig的getTemplate()方法获取一个ParrotTemplate对象,不仅支持通过索引的get操作,也能够支持scan扫描的find方式。
当我们使用完ParrotTable或者ParrotTemplate后,Parrot会自动管理其中的连接,无需开发人员手动进行资源的释放,并且使用ParrotTemplate执行数据检索的时候,结果集会自动被映射到实体Bean中返回,而无需开发人员手动进行赋值操作。
除了对Table进行CRUD操作,我们还可以通过getTable()方法获取到MetaDateTable对象,该对象用于执行HBase中的元数据操作,比如表的创建和删除,使用方式如下所示:
@Test
publicvoid testDropTable() throws Exception {
MetaDataTable table = conn.getTable();
table.drop(Bean1.class);
}
@Test
publicvoid testCreateTable() throws Exception {
MetaDataTable table = conn.getTable();
table.create(Bean1.class);
}
@Test
publicvoid testCreateTables() throws Exception {
MetaDataTable table = conn.getTable();
List<Class> classes = new ArrayList<Class>();
classes.add(Bean1.class);
classes.add(Bean2.class);
table.create(classes);
}
@Test
publicvoid testDropTables() throws Exception {
MetaDataTable table = conn.getTable();
List<Class> classes = new ArrayList<Class>();
classes.add(Bean1.class);
classes.add(Bean2.class);
table.drop(classes);
}
四、Parrot所支持的原生HBase特性
当Parrot不满足于我们的业务场景时怎么办?这个时候可以使用ConnectionConfig接口中提供的getHTableInterface()方法,该方法会返回一个HTableInterface对象,该对象就是HBase的原生Table对象,开发人员可以使用该对象完成HBase的CRUD操作,如下所示:
@Test
publicvoid testHTableInterface() throws Exception {
/* HBase的原生操作 */
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParrotTable parrotTable = conn.getTable(Bean1.class);
HTableInterface table = parrotTable.getHTableInterface();
Scan can = new Scan();
can.setStartRow(Bytes.toBytes("key"));
can.setTimeRange(format.parse("2014-05-23 00:00:00").getTime(), format
.parse("2014-05-24 00:00:00").getTime());
ResultScanner rts = table.getScanner(can);
Result rt = null;
while ((rt = rts.next()) != null) {
System.out.println(Bytes.toString(rt.getValue("info".getBytes(),
"userName".getBytes())));
System.out.println(Bytes.toString(rt.getValue("info".getBytes(),
"passWord".getBytes())));
}
parrotTable.setAutoFlush(table);
}
一旦你在程序中使用原生的HTableInterface,则默认不再受到Parrot的管理,所有的资源则需要自己释放,所有的赋值操作则需要自己手动完成,Parrot将不会再负责结果集映射。
五、Parrot中被废弃的事物操作
刚开始设计Parrot的时候,我的想象非常美好。那就是在Parrot中添加事物支持,哪怕是“伪事物”也可以。但这一切都是错误的开始。
我们知道HBase0.94版本之前,可以说是根本没有提供事物这个操作的,但0.94版本之后却可以支持弱事物,所谓弱事物指的就是支持同一行中的多个put或delete操作,但跨行、跨表、跨Region则不行,那么我当时的思路是提供Annotation的方式实现事物的AOP横切管理,当有多个事物执行的时候,无论是否在同一个表或者同一个region中,我们只需要做到put或delete失败就回滚,回滚的操作比较复杂,put的话就对应delete,delete就对应put,这样一来就无需顾虑是否是跨行、跨表、跨Region了。
想象是美好的,但现实的残酷是,delete失败后,执行回滚,我不仅要保存delete之前的数据状态,且回滚时,我如何保证数据正确落入到删除之前的region中?如何确保插入的时间戳与数据真实insert前是一致的?等等这些问题折腾了一天,所以彻底死心宣布Parrot废弃事物,目前只能等待后续HBase版本的更新支持是事物后,再说!
六、Parrot的设计实现;
没时间写了,以后再说,主要是没建模工具,懒得画图。