HBase是什么

最近学习了HBase,正常来说写这篇文章,应该从DB有什么缺点,HBase如何弥补DB的缺点开始讲会更有体感,但是本文这些暂时不讲,只讲HBase,把HBase相关原理和使用讲清楚,后面有一篇文章会专门讲DB与NoSql各自的优缺点以及使用场景。

HBase是谷歌Bigtable的开源版本,2006年谷歌发布《Bigtable:A Distributed Storage System For Structured Data》论文之后,Powerset公司就宣布HBase在Hadoop项目中成立,作为子项目存在。后来,在2010年左右逐渐成为Apache旗下的一个顶级项目,因此HBase名称的由来就是由于其作为Hadoop Database存在的,用于存储非结构化、半结构化的数据。

下图展示了HBase在Hadoop生态中的位置:

可以看到HBase建立在HDFS上,HBase内部管理的文件全部都是存储在HDFS中,同时MapReduce这个计算框架在HBase之上又提供了高性能的计算能力来处理海量数据。

HBase的特点与不足

HBase的基本特点概括大致如下:

  • 海量数据存储(PB)级别,在PB级别数据以及采用廉价PC存储的情况下,数据能在几十到百毫秒内返回数据
  • 高可用,WAL + Replication机制保证集群异常不会导致写入数据丢失与数据损坏,且HBase底层使用HDFS,HDFS本身也有备份
  • 数据写入性能强劲
  • 列式存储,和传统数据库行式存储有本质的区别,这个在之后HBase存储原理的时候详细解读
  • 半结构化或非结构化数据存储
  • 存储稀松灵活,列数据为空的情况下不占据存储空间
  • 同一份数据,可存储多版本号数据,方便历史数据回溯
  • 行级别事务,可以保证行级别数据的ACID特性
  • 扩容方便,无需数据迁移,及扩即用

当然事事不是完美的,HBase也存在着以下两个最大的不足:

  • 无法做到条件查询,这是最大的问题,假如你的代码中存在多个查询条件,且每次使用哪个/哪组查询条件不确定,那么使用HBase是不合适的,除非数据冗余,设计多份RowKey
  • 做不了分页,数据总记录数几乎无法统计,因为HBase本身提供的表行数统计功能是一个MapReduce任务,极为耗时,既然拿不到总记录数,分页总署也没法确定,自然分页也无法做了

总的来说,对于HBase需要了解以上的一些个性应该大致上就可以了,根据HBase的特点与不足,在合适的场景下选择使用HBase,接下来针对HBase的一些知识点逐一解读。

HBase的基本架构

下图是HBase的基本架构:

从图上可以看到,HBase中包含的一些组件如下:

  • Client----包含访问HBase的接口
  • Zookeeper----通过选举保证任何时候集群中只有一个HMaster、HMaster与Region Server启动时向注册、存储所有Region的寻址入口、实时监控Region Server的上下线信息并实时通知给HMaster、存储HBase的Schema与Table原数据
  • HMaster----为Region Server分配Region、负责Region Server的负载均衡、发现失效的Region Server并重新分配其上的Region、管理用户对Table的增删改查
  • Region Server----维护Region并处理对Region的IO请求、切分在运行过程中变得过大的Region

其中,Region是分布式存储和负载均衡中的最小单元,不过并不是存储的最小单元。Region由一个或者多个Store组成,每个Store保存一个列簇;每个Store又由一个memStore和0~N个StoreFile组成,StoreFile包含HFile,StoreFile只是对HFile做了轻量级封装,底层就是HFile。

介于上图元素有点多,我这边画了一张图,把HBase架构中涉及的元素的关系理了一下:

HBase的基本概念

接着看一下HBase的一些基本概念,HBase是以Table(表)组织数据的,一个Table中有着以下的一些元素:

  • RowKey(行键)----即关系型数据库中的主键,它是唯一的,在HBase中这个主键可以是任意的字符串,最大长度为64K,在内部存储中会被存储为字节数组,HBase表中的数据是按照RowKey的字典序排列的。例如1、2、3、4、5、10,按照自然数的顺序是这样的,但是在HBase中1后面跟的是10而不是2,因此在设计RowKey的时候一定要充分利用字典序这个特性,将一下经常读取的行存储到一起或者靠近,减少Scan耗时,提高读取的效率
  • Column Family(列族)----表Schema的一部分,HBase表中的每个列都归属于某个列族,即列族是由一系列的列组成的,必须在创建表的时候就指定好。列明都以列族作为前缀,例如courses:history、courses:math都属于courses这个列族。列族不是越多越好,过多的列族会导致io增多及分裂时数据不均匀,官方推荐列族数量为1~3个。列族不仅能帮助开发者构建数据的语义边界,还能有助于开发者设置某些特性,例如可以指定某个列族内的数据压缩形式。访问控制、磁盘和内存怒的使用统计都是在列族层面进行的
  • Column(列)----一般从属于某个列族,列的数量一般没有强限制,一个列族中可以有数百万列且这些列都可以动态添加
  • Version Number(版本号)----HBase中每一列的值或者说每个单元格的值都是具有版本号的,默认使用系统当前时间,精确到毫秒,也可以用户显式地设置。每个单元格中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。另外,为了避免数据存在过多版本造成的管理(存储 + 索引)负担,HBase提供了两种数据版本回收的方式,一是保存数据的最后n个版本,二是保存最近一段时间内的版本,用户可以针对每个列族进行设置
  • Cell(单元格)----一个单元格就是由RowKey、Column Family:Column、Version Number唯一确定的,Cell中的数据是没有类型的,全部都是字节码

另外一个概念就是,访问HBase Table中的行,只有三种方式:

  • 通过单个Row Key访问
  • 通过Row Key的range
  • 全表扫描

这部分介绍的Table、RowKey、Column Family、Column等都属于逻辑概念,而上部分中的Region Server、Region、Store等都属于物理概念,下图展示了逻辑概念与物理概念之间的关系:

即:table和region是一对多的关系,因为table的数据可能被打在多个region中;region和columnFamily是一对多的关系,一个store对应一个columnFamily,一个region可能对应多个store

HBase的逻辑表视图与物理表视图

接着看一下HBase中的表逻辑视图与物理视图。首先是逻辑表视图:

看到这里定义了2个列族,一个Personal Info、一个Family Info,对应到数据库中,相当于把两张表合并到一个一起。

从逻辑视图看,上图由ZhangSan、LiSi两行组成,但是在实际物理存储上却不是按照这种方式进行的存储:

看到主要是有两点差别:

  • 一行被拆开了,按照列族进行存储
  • 空列不会被存储,例如LiSi在Peronal Info中没有Provice与Phone,在Family Info中没有Brother

HBase的增删改查

光说不练假把式,不能光讲理论,代码也是要有的,为了方便起见,我用的是阿里云HBase,和HBase一样,只是省去了运维成本。当然虽然本人是内部员工,但是工作之外的学习是不会占用公司资源的^_^悄悄告诉大家,阿里云HBase有个福利,第一个月免费试用,想同样玩一下HBase的可以去阿里云搞一个。

首先添加一下pom依赖,用阿里云指定的HBase,使用上和原生的HBase API一模一样:

<dependency>
<groupId>com.aliyun.hbase</groupId>
<artifactId>alihbase-client</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>

注意一下第二个dependency,jdk.tools不添加pom文件可能会报错"Missing artifact jdk.tools:jdk.tools:jar:1.8",错误原因是tools.jar包是JDK自带的,pom.xml中的包隐式依赖tools.jar包,而tools.jar并未在库中,因此需要将tools.jar包添加到jdk库中。

首先写个HBaseUtil,用单例模式来写,好久没写了,顺便练习一下:

 /**
* 五月的仓颉https://www.cnblogs.com/xrq730/p/11134806.html
*/
public class HBaseUtil { private static HBaseUtil hBaseUtil; private Configuration config = null; private Connection connection = null; private Map<String, Table> tableMap = new HashMap<String, Table>(); private HBaseUtil() { } public static HBaseUtil getInstance() {
if (hBaseUtil == null) {
synchronized (HBaseUtil.class) {
if (hBaseUtil == null) {
hBaseUtil = new HBaseUtil();
}
}
} return hBaseUtil;
} /**
* 初始化Configuration与Connection
*/
public void init(String zkAddress) {
config = HBaseConfiguration.create();
config.set(HConstants.ZOOKEEPER_QUORUM, zkAddress); try {
connection = ConnectionFactory.createConnection(config);
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
} /**
* 创建table
*/
public void createTable(String tableName, byte[]... columnFamilies) {
// HBase创建表的时候必须创建指定列族
if (columnFamilies == null || columnFamilies.length == 0) {
return ;
} TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
for (byte[] columnFamily : columnFamilies) {
tableDescriptorBuilder.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(columnFamily).build());
} try {
Admin admin = connection.getAdmin();
admin.createTable(tableDescriptorBuilder.build());
// 这个Table连接存入内存中
tableMap.put(tableName, connection.getTable(TableName.valueOf(tableName)));
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
} } public Table getTable(String tableName) {
Table table = tableMap.get(tableName);
if (table != null) {
return table;
} try {
table = connection.getTable(TableName.valueOf(tableName));
if (table != null) {
// table对象存入内存
tableMap.put(tableName, table);
} return table;
} catch (IOException e) {
e.printStackTrace();
return null;
}
} }

注意,HBase中的数据一切皆二进制,因此从上面代码到后面代码,字符串全部都转换成了二进制。

接着定义一个BaseHBaseUtilTest类,把一些基本的定义放在里面,保持主测试类清晰:

 /**
* 五月的仓颉https://www.cnblogs.com/xrq730/p/11134806.html
*/
public class BaseHBaseUtilTest { protected static final String TABLE_NAME = "student"; protected static final byte[] COLUMN_FAMILY_PERSONAL_INFO = "personalInfo".getBytes(); protected static final byte[] COLUMN_FAMILY_FAMILY_INFO = "familyInfo".getBytes(); protected static final byte[] COLUMN_NAME = "name".getBytes(); protected static final byte[] COLUMN_AGE = "age".getBytes(); protected static final byte[] COLUMN_PHONE = "phone".getBytes(); protected static final byte[] COLUMN_FATHER = "father".getBytes(); protected static final byte[] COLUMN_MOTHER = "mother".getBytes(); protected HBaseUtil hBaseUtil; }

第一件事情,创建Table,注意前面说的,HBase必须Table和列族一起创建:

 /**
* 五月的仓颉https://www.cnblogs.com/xrq730/p/11134806.html
*/
public class HBaseUtilTest extends BaseHBaseUtilTest { @Before
public void init() {
hBaseUtil = HBaseUtil.getInstance();
hBaseUtil.init("xxx");
} /**
* 创建表
*/
@Test
public void testCreateTable() {
hBaseUtil.createTable(TABLE_NAME, COLUMN_FAMILY_PERSONAL_INFO, COLUMN_FAMILY_FAMILY_INFO);
} }

我自己申请的HBase,zk地址就不给大家看啦,如果同样申请了的,替换一下就好了。testCreateTable方法运行一下,就创建好了student表。接着利用put创建四条数据,多创建几条,等下scan可以测试:

 /**
* 添加数据
*/
@Test
public void testPut() throws Exception {
Table table = hBaseUtil.getTable(TABLE_NAME);
// 用户1,用户id:12345
Put put1 = new Put("12345".getBytes());
put1.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_NAME, "Lucy".getBytes());
put1.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE, "18".getBytes());
put1.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_PHONE, "13511112222".getBytes());
put1.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_FATHER, "LucyFather".getBytes());
put1.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_MOTHER, "LucyMother".getBytes());
// 用户2,用户id:12346
Put put2 = new Put("12346".getBytes());
put2.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_NAME, "Lily".getBytes());
put2.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE, "19".getBytes());
put2.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_PHONE, "13522223333".getBytes());
put2.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_FATHER, "LilyFather".getBytes());
put2.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_MOTHER, "LilyMother".getBytes());
// 用户3,用户id:12347
Put put3 = new Put("12347".getBytes());
put3.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_NAME, "James".getBytes());
put3.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE, "22".getBytes());
put3.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_FATHER, "JamesFather".getBytes());
put3.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_MOTHER, "JamesMother".getBytes());
// 用户4,用户id:12447
Put put4 = new Put("12447".getBytes());
put4.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_NAME, "Micheal".getBytes());
put4.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE, "22".getBytes());
put2.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_PHONE, "13533334444".getBytes());
put4.addColumn(COLUMN_FAMILY_FAMILY_INFO, COLUMN_MOTHER, "MichealMother".getBytes()); table.put(Lists.newArrayList(put1, put2, put3, put4));
}

同样的,运行一下testPut方法,四条数据就创建完毕了。注意为了提升处理效率,HBase的get、put这些API都提供的批量处理方式,这样一次提交可以提交多条数据,发起一次请求即可,不用发起请求。

接着看一下利用Get API查询数据:

 /**
* 获取数据
*/
@Test
public void testGet() throws Exception {
Table table = hBaseUtil.getTable(TABLE_NAME);
// get1,拿到全部数据
Get get1 = new Get("12345".getBytes());
// get2,只拿personalInfo数据
Get get2 = new Get("12346".getBytes());
get2.addFamily(COLUMN_FAMILY_PERSONAL_INFO); Result[] results = table.get(Lists.newArrayList(get1, get2));
if (results == null || results.length == 0) {
return ;
} for (Result result : results) {
printResult(result);
}
} private void printResult(Result result) {
System.out.println("====================分隔符====================");
printBytes(result.getValue(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_NAME));
printBytes(result.getValue(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE));
printBytes(result.getValue(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_PHONE));
printBytes(result.getValue(COLUMN_FAMILY_FAMILY_INFO, COLUMN_FATHER));
printBytes(result.getValue(COLUMN_FAMILY_FAMILY_INFO, COLUMN_MOTHER));
} private void printBytes(byte[] bytes) {
if (bytes != null && bytes.length != 0) {
System.out.println(new String(bytes));
}
}

HBase查询数据比较灵活的是,可以查询RowKey下对应的所有数据、可以按照RowKey-Column Family的维度查询数据、可以按照RowKey-Column Family-Column的维度查询数据,也可以按照RowKey-Column Family-Column-Timestamp的维度查询数据,可以查询Timestamp区间内的数据,也可以查询RowKey-Column Family-Column下所有Timestamp数据。上面的代码执行结果为:

====================分隔符====================
Lucy
18
13511112222
LucyFather
LucyMother
====================分隔符====================
Lily
19
13533334444

和我们的预期相符,即"12345"这个RowKey查询出了所有数据,"12346"这个RowKey只查了personalInfo这个列族的数据。

最后这一部分我们看一下更新,更新的API和新增的API都是一样的,都是Put:

@Test
public void testUpdate() throws Exception {
Table table = hBaseUtil.getTable(TABLE_NAME);
// 用户1,用户id:12345
Put put = new Put("12346".getBytes());
put.addColumn(COLUMN_FAMILY_PERSONAL_INFO, COLUMN_AGE, 1, "22".getBytes());
table.put(put);
}

Get看一下执行12346这条数据的值:

Lily
19
13533334444

看到12346对应的数据,原本Age是19,更新到22,依然是19,这就是一个值得注意的点了。HBase的更新其实是往Table里面新增一条记录,按照Timestamp进行排序,最新的数据在前面,每次Get的时候将第一条数据取出来。在这里我们指定的Timestamp=1,这个值落后于先前插入的Timestamp,自然就排在后面,因此读取出来的Age依然是原值19,这个细节特别注意一下。

HBase的Scan

感觉前面篇幅有点大,所以这里专门抽一个篇幅出来写一下Scan,Scan是HBase扫描数据的方式。

首先可以看一下最基本的Scan:

 /**
* 扫描
*/
@Test
public void testScan() throws Exception {
Table table = hBaseUtil.getTable(TABLE_NAME);
Scan scan = new Scan().withStartRow("12345".getBytes(), true).withStopRow("12347".getBytes(), true); ResultScanner rs = table.getScanner(scan);
if (rs != null) {
for (Result result : rs) {
printResult(result);
}
}
}

执行结果为:

====================分隔符====================
Lucy
19
13511112222
LucyFather
LucyMother
====================分隔符====================
Lily
19
13533334444
LilyFather
LilyMother
====================分隔符====================
James
22
JamesFather
JamesMother

表示查询12345~12347这个范围内的所有RowKey,withStartRow的第二个参数true表示包含,如果为false那么12345这个RowKey就查不出来了。

进阶的,HBase为我们提供了带过滤器的Scan,一共有十来种,我这边只演示两种以及组合的情况,其他的查询一下HBase API文档即可,2.1版本的API文档地址为http://hbase.apache.org/2.1/apidocs/index.html。演示代码如下:

 @Test
public void testScanFilter() throws Exception {
Table table = hBaseUtil.getTable(TABLE_NAME); System.out.println("********************RowFilter测试********************");
Scan scan0 = new Scan().withStartRow("12345".getBytes(), true);
scan0.setFilter(new RowFilter(CompareOperator.EQUAL, new BinaryComparator("12346".getBytes())));
ResultScanner rs0 = table.getScanner(scan0);
printResultScanner(rs0); System.out.println("********************PrefixFilter测试********************");
Scan scan1 = new Scan().withStartRow("12345".getBytes(), true);
scan1.setFilter(new PrefixFilter("124".getBytes()));
ResultScanner rs1 = table.getScanner(scan1);
printResultScanner(rs1); System.out.println("********************两种Filter同时满足测试********************");
Scan scan2 = new Scan().withStartRow("12345".getBytes(), true);
Filter filter0 = new RowFilter(CompareOperator.EQUAL, new BinaryComparator("12447".getBytes()));
Filter filter1 = new PrefixFilter("124".getBytes());
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL, filter0, filter1);
scan2.setFilter(filterList);
ResultScanner rs2 = table.getScanner(scan2);
printResultScanner(rs2);
}

执行结果为:

********************RowFilter测试********************
====================分隔符====================
Lily
19
13533334444
LilyFather
LilyMother
********************PrefixFilter测试********************
====================分隔符====================
Micheal
22
MichealMother
********************两种Filter同时满足测试********************
====================分隔符====================
Micheal
22
MichealMother

总的来说,HBase本质上是KV型NoSql,根据Key查询Value是最高效的,Scan这个API还是慎用,范围里面的数据量小倒无所谓,一旦RowKey设计不合理,StartRow和EndRow没有指定好,可能会造成大范围的扫描,降低HBase整体能力。

HBase和KV型缓存的区别

看了上面的代码演示,不知道大家有没有和我一开始有一样的疑问:HBase看上去也是K-V形式的,那么它和支持KV型数据的缓存(例如Redis、MemCache、Tair)有什么区别?

我用一张表格总结一下二者的区别:

总的来说,同样作为数据库的NoSql替代方案,HBase更加适合用于海量数据的持久化场景,KV型缓存更加适合用于对数据的高性能读写上。

HBase的Region分裂及会导致的热点问题

经典问题,首先看一下什么是Region分裂,只把Region分裂讲清楚,不讲具体Region分裂的实现方式,理由也很简单,Region分裂细节学得再清楚,对工作中的帮助也不大,没必要太过于追根究底。

Region分裂是HBase能够拥有良好扩张性的最重要因素之一,也必然是所有分布式系统追求无限扩展性的一副良药。通过前面的部分我们知道HBase的数据以行为单位存储在HBase表中,HBase表按照多行被分割为多个Region,这个Region分布在HBase集群中,并且由Region Server进程负责讲这些Region提供给Client访问。一个Region中,RowKey是一个连续的范围,也就是说表中的记录在Region中是按照startKey到endKey的范围为RowKey进行排序存储的。通常一个表由多个Region构成,这些Region分布在多个Region Server上,也就是说,Region是在Region Server中插入和查询数据时负载均衡的物理机制。一张HBase表在刚刚创建的时候默认只有一个Region,所以关于这张表的请求都被路由到同一个Region Server,无论集群中有多少Region Server,而一旦某个Region的大小达到一定值,就会自动分裂为两个Region,这也就是为什么HBase表在刚刚创建的阶段不能充分利用整个集群吞吐量的原因。

在HBase管理界面可以查看每个Region,startKey与endKey的范围,例如(图片来自网络):

这里特别注意一个点,RowKey是按照Key的字符自然顺序进行排序的,因此RowKey=9的Key,会落在最后一个Region Server中而不是第一个Region Server中

那么什么是热点问题应该也很好理解了:

虽然HBase的单机读写性能强劲,但是当集群中成千上万的请求RowKey都落在aaaaa-ddddd之间,那么这成千上万请求最终落到Region Server1这台服务器上,一旦超出服务器自身承受能力,那么必然导致服务器不可用甚至宕机。因此我们说设计RowKey的时候千万把时间戳或者id自增的方式作为RowKey方案就是这个道理,时间戳或者id自增的方式,虽然最终可以让RowKey落到不同的Region中,但是在当下或者当下往后的一段时间内,RowKey一定是会落到同一个Region中的,数据热点问题将严重影响HBase集群能力。

解决热点问题通常有两个方案,最初级的方案是设置预分区,即在Table创建的时候就先设置几个Region,为每个Region划分不同的startKey与endKey,但这么做有以下两个缺点:

  • 高度依赖RowKey,必须事先知道插入数据的RowKey的分布
  • 即使事先知道插入数据的RowKey分布,但是如果数据分布不均匀或者存在热点行,依然无法均匀分摊负载

但是无论如何,设置预分区依然是一种解决热点问题的方案。

第二个解决方案是一劳永逸的解决方案也是使用HBase最核心的一个点:合理设计RowKey。即让RowKey均匀分布在Region中,大致有以下几个方案可供参考:

  • 倒序。例如手机号码135ABCD、135EFGH、135IJKL这种,前缀没有区分度,非常容易落到相同的Region中,此时做倒序即DCBA531、HGFE531、LKJI531,将有区分度的部分放在前面,就非常容易将数据散落在不同的Region中
  • 原数据加密。例如做MD5,因为MD5的随机性是非常强的,因此做了MD5后,数据将会非常分散
  • 加随机前缀。例如ASCII码中随机选5位作为数据前缀,同样可以达到分散RowKey的效果,但是缺点是必须记住每个原数据对应的前缀

无论如何,还是那句话,合理设计RowKey是HBase使用的核心。

WAL机制

最后讲一下前面提到的WAL机制,WAL的全称为Write Ahead Log,它是HBase的RegionServer在处理数据插入和删除的过程中用来记录操作内容的一种日志,是用来做灾难恢复的。

其实WAL并不是什么新鲜思想,在数据库领域很常见:

  • mysql有binlog,记录每一次数据变更
  • redis有aof,在开启aof的情况下,每隔短暂时间,将这段时间产生的操作记录文件

其核心都是,变更数据前先写磁盘日志文件,在系统发生异常的时候,重放日志文件对数据进行恢复,HBase的WAL机制也是一样的思想,数据变更步骤为:

  • 首先从之前的图上可以看到有HLog,HLog是实现WAL的类,一个RegionServer对应一个HLog,多个Region共享一个HLog,不过从HBase1.0版本开始可以定义多个HLog以提高吞吐量
  • 客户端的一次数据提交先写HLog,这个是告知客户端数据提交成功的前提
  • HLog写入成功后写入MemStore
  • 当MemStore的值达到一定程度后,flush到hdfs,形成一个一个的StoreFile(HFile)
  • flush过后,HLog中对应的数据就没用了,删除

因为有了HLog,即使在MemStore中的数据还没有flush到hdfs的时候系统发生了宕机或者重启,数据都不会出现丢失。

最近学习了HBase的更多相关文章

  1. HBase 学习之一 <<HBase使用客户端API动态创建Hbase数据表并在Hbase下导出执行>>

    HBase使用客户端API动态创建Hbase数据表并在Hbase下导出执行                       ----首先感谢网络能够给我提供一个开放的学习平台,如果没有网上的技术爱好者提供 ...

  2. 《HBase in Action》 第二章节的学习总结 ---- HBase基本组成

    准备工作:采用的HBase版本是:CDH4.5,其中的Hadoop版本是:hadoop-2.0.0-cdh4.5.0:HBase版本是:hbase-0.94.6-cdh4.5.0: Hbase的配置文 ...

  3. HBase学习笔记-HBase性能研究(1)

    使用Java API与HBase集群交互时,需要构建HTable对象,使用该对象提供的方法来进行插入/删除/查询等操作.要创建HTable对象,首先要创建一个带有HBase集群信息的配置对象Confi ...

  4. Hadoop学习之HBase和Hive的区别

    Hive是为简化编写MapReduce程序而生的,使用MapReduce做过数据分析的人都知道,很多分析程序除业务逻辑不同外,程序流程基本一样.在这种情况下,就需要Hive这样的用户编程接口.Hive ...

  5. Hadoop学习之HBase

    1. HBase有哪些基本的特征? 2. HBase相对于关系数据库能解决的问题是什么? 3. HBase的数据模型是什么?如何表述?有哪些操作形式? 4. HBase的模式Schema设计的一些概念 ...

  6. HBase学习——4.HBase过滤器

    1.过滤器 基础API中的查询操作在面对大量数据的时候是非常苍白的,这里Hbase提供了高级的查询方法:Filter.Filter可以根据簇.列.版本等更多的条件来对数据进行过滤,基于Hbase本身提 ...

  7. HBase学习——3.HBase表设计

    1.建表高级属性 建表过程中常用的shell命令 1.1 BLOOMFILTER 默认是 NONE 是否使用布隆过虑及使用何种方式,布隆过滤可以每列族单独启用 使用HColumnDescriptor. ...

  8. Hbase 学习(一) hbase配置文件同步

    最近在狂啃hadoop的书籍,这部<hbase:权威指南>就进入我的视野里面了,啃吧,因为是英文的书籍,有些个人理解不对的地方,欢迎各位拍砖. HDFS和Hbase配置同步 hbase的配 ...

  9. Hadoop学习---Zookeeper+Hbase配置学习

    软件版本号: JDK:jdk-8u45-linux-i586.tar.gz Zookeeper:zookeeper-3.4.6 Hbase:hbase-1.0.0-bin 一.JDK版本更换 由于之前 ...

随机推荐

  1. Prism框架在项目中使用

    本文大纲 1.Prism框架下载和说明 2.Prism项目预览及简单介绍. 3.Prism框架如何在项目中使用. Prism框架下载和说明 Prism框架是针对WPF和Silverlight的MVVM ...

  2. js 动态操作元素

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  3. js 点击超链接,执行js脚本,而不进行url跳转

    <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><meta ...

  4. This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.

    Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...

  5. 海洋cms 模板标签手册

    海洋cms采用极其简单易用的模板技术,所有标签直接调用接口,无需复杂的编码技术,让你对界面设计得心应手,请认真阅读本文档,妥善收藏. ========= 目录 =========00.相关必要说明01 ...

  6. Django 下添加左侧字段显示和搜索

    在对应的apps下建立xadmin.py from .models import EmailVerifyRecord import xadminclass EmailVerifyRecordAdmin ...

  7. 在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一(超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现)

    在计算机科学领域中,Unicode(统一码.万国码.单一码.标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统.Unicode 是基于通用字符集(Universal Charac ...

  8. List遍历删除 或取指定的前N项

    class Program { static void Main(string[] args) { /* * List遍历删除 或取指定的前N项 */ List<PerSon> listP ...

  9. Android 动画基础——视图动画(View Animation)

    本篇讲android 3.0之前被广泛的动画框架——ViewAnimation. 目录 我将分为六部分来讲: 概述 Alpha透明动画 Rotate旋转动画 Translate位移动画 Scale放缩 ...

  10. Java8 的一些新特性总结

    目前Java8已经发布很多个版本了,对于Java8中的新特性虽然有各位大神进行jdk8的英文特性文档翻译,但都太官方化语言,对照几篇翻译本人对新特性文档做一下总结,以帮助我和各位不了解Java8新特性 ...