hbasescan(hbase的scan用法)

本篇文章给大家谈谈hbasescan,以及hbase的scan用法对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

hbase多个scan能一起查吗

可以一起查。

1.scan原理

HBase的查询实现昌蠢只提供两种方式:

1、按指定RowKey 获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get)

Get 的方法处理分两种 : 设置了ClosestRowBefore 和没有设置的rowlock .主要是用来保证行的事务性,即每个get 是以一个row 来标记的.一个row中可以有很多family 和column.

2、按指定的条件获取一批记录,scan方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的就是scan 方式.

1)scan 可以通过setCaching 与setBatch 方法提高速度(以空间换时间);

2)scan 可以通过setStartRow 与setEndRow 来限定范围([start,end)start 是闭区间,

end 是开区间)。范围越小,性能越高。

3)、scan 可以通过setFilter 方法添加过滤器,这也是分页、多条件查询的基础。

HBase中scan并不像大家想象的一样直接发送一个命令过去,服务器就将满足扫描条件的所有数据一次性返回给客户端。而实际上它的工作原理如下图所示:

上图右侧是HBase scan的客户端代码,其中for循环中每次遍历ResultScanner对象获取一行记录,实际上在客户端层面都会调用一次next请求。next请求整个流程可以分为如下几个步骤:

next请求首先会检汪核查客户端缓存中是否存在还没有读取的数据行,如果有就直接返回,否则需要将next请求给HBase服务器端(RegionServer)。

如果客户端缓存已经没有扫描结果,就会将next请求发送给HBase服务器端。默认情况下,一次next请求仅可以请求100行数据(或者返回结果集总大小不超过2M)

服务器端接收到next请求之后就开始从BlockCache、HFile以及memcache中一行一行进行扫描,扫描的行数达到100行之后就返回给客户端,客户端将这100条数据缓存到内存并返回一条给上层业务。

HBase 每次 scan 的数据量可能会比较大,客户端不会一次性全部把数据从服务端拉回来。而是通过多次 rpc 分批次的拉取。类似于 TCP 协议里面一段一段的传输,可以做到细粒度的流量控制。至于如何调优,控制每次 rpc 拉取的数据量,就可以通过三个参数来控制。

.setCaching = .setNumberOfRowsFetchSize (客户端每次 rpc fetch 的行数)

.setBatch = .setColumnsChunkSize (客户端每次获取的列数)

.setMaxResultSize = .setMaxResultByteSize (客户端缓存的最大字节数)

hbase.client.scanner.caching - (setCaching):HBase-0.98 默认值为为 100,HBase-1.2 默认值为 2147483647,即 Integer.MAX_VALUE。Scan.next() 的一次 RPC 请求 fetch 的记录条数。配置建议:这个参数与下面的setMaxResultSize配合使用,在网络状况良好的情况下,自定义设置不宜太小, 可以直接采用默认值,不配置。

setBatch() 配置获取的列数,假如表有两个列簇 cf,info,每个列簇5个列。这样每行可能有10列了,setBatch() 可以控制每次获取的最大列数,进一步从列级别控制流量。配置建议:当列数很多,数据量大时考虑配置此参数,例如100列每次只获取50列。一般情况可以默认值(-1 不受限)。

hbase.client.scanner.max.result.size - (setMaxResultSize):HBase-0.98 无该项配置,HBase-1.2 默认值为 210241024,即 2M。Scan.next() 的一次 RPC 请求 fetch 的数据量大小,目前 HBase-1.2 在 Caching 为默认值(Integer Max)的时候,实际使用这个参数控制 RPC 次数和流量。配置建耐陵陪议:如果网络状况较好(万兆网卡),scan 的数据量非常大,可以将这个值配置高一点。如果配置过高:则可能 loadCache 速度比较慢,导致 scan timeout 异常

hbase.server.scanner.max.result.size:服务端配置。HBase-0.98 无该项配置,HBase-1.2 新增,默认值为 10010241024,即 100M。该参数表示当 Scan.next() 发起 RPC 后,服务端返回给客户端的最大字节数,防止 Server OOM。

要计算一次扫描操作的RPC请求的次数,用户需要先计算出行数和每行列数的乘积。然后用这个值除以批量大小和每行列数中较小的那个值。最后再用除得的结果除以扫描器缓存值。 用数学公式表示如下:

RPC 返回的个数 = (row数 * 每行的列数)/ Min(每行列数,Batch大小) / Caching大小

Result 返回的个数 =( row数 * 每行的列数 )/ Min(每行列数,Batch大小)

复制

2.Hbase Shell中使用

在hbase shell中查询数据,可以在hbase shell中直接使用过滤器:

# hbase shell scan 'tablename',STARTROW='start',COLUMNS=['family:qualifier'],FILTER="ValueFilter(=,'substring:88')"

复制

如上命令所示,查询的是表名为testByCrq,过滤方式是通过value过滤,匹配出value含111的数据。

因在hbase shell中一些操作比较麻烦(比如删除字符需先按住ctrl在点击退格键),且退出后,查询的历史纪录不可考,故如下方式是比较方便的一种:

# echo "scan 'testByCrq', FILTER=\"ValueFilter(=,'substring:111')\"" | hbase shell

复制

如上命令,可在bash中直接使用,表名是testByCrq,过滤方式是通过value过滤,匹配出value含111的数据,中间的"需要用\转义。

建表

create 'test1', 'lf', 'sf'

-- lf: column family of LONG values (binary value)

-- sf: column family of STRING values

复制

导入数据

put 'test1', 'user1|ts1', 'sf:c1', 'sku1'

put 'test1', 'user1|ts2', 'sf:c1', 'sku188'

put 'test1', 'user1|ts3', 'sf:s1', 'sku123'

put 'test1', 'user2|ts4', 'sf:c1', 'sku2'

put 'test1', 'user2|ts5', 'sf:c2', 'sku288'

put 'test1', 'user2|ts6', 'sf:s1', 'sku222'

put 'test1', 'user3|ts7', 'lf:c1', 12345

put 'test1', 'user3|ts8', 'lf:c1', 67890

复制

1.限制条件

scan 'hbase:meta'

scan 'hbase:meta', {COLUMNS = 'info:regioninfo'}

scan 'ns1:t1', {COLUMNS = ['c1', 'c2'], LIMIT = 10, STARTROW = 'xyz'}

scan 't1', {COLUMNS = ['c1', 'c2'], LIMIT = 10, STARTROW = 'xyz'}

scan 't1', {COLUMNS = 'c1', TIMERANGE = [1303668804, 1303668904]}

scan 't1', {REVERSED = true}

复制

2.Filter过滤

1.rowkey查询

rowkey为user1开头的

scan 'test1', FILTER = "PrefixFilter ('user1')"

ROW COLUMN+CELL

user1|ts1 column=sf:c1, timestamp=1409122354868, value=sku1

user1|ts2 column=sf:c1, timestamp=1409122354918, value=sku188

user1|ts3 column=sf:s1, timestamp=1409122354954, value=sku123

复制

FirstKeyOnlyFilter: 一个rowkey可以有多个version,同一个rowkey的同一个column也会有多个的值, 只拿出key中的第一个column的第一个version KeyOnlyFilter: 只要key,不要value

scan 'test1', FILTER="FirstKeyOnlyFilter() AND ValueFilter(=,'binary:sku188') AND KeyOnlyFilter()"

ROW COLUMN+CELL

user1|ts2 column=sf:c1, timestamp=1409122354918, value=

复制

查询rowkey里面包含ts3的

scan 'test1', FILTER="RowFilter(=,'substring:ts3')"

ROW COLUMN+CELL

user1|ts3 column=sf:s1, timestamp=1554865926412, value=sku123

复制

从user1|ts2开始,找到所有的rowkey以user1开头的

scan 'test1', {STARTROW='user1|ts2', FILTER = "PrefixFilter ('user1')"}

ROW COLUMN+CELL

user1|ts2 column=sf:c1, timestamp=1409122354918, value=sku188

user1|ts3 column=sf:s1, timestamp=1409122354954, value=sku123

复制

从user1|ts2开始,找到所有的到rowkey以user2开头

scan 'test1', {STARTROW='user1|ts2', STOPROW='user2'}

ROW COLUMN+CELL

user1|ts2 column=sf:c1, timestamp=1409122354918, value=sku188 user1|ts3 column=sf:s1, timestamp=1409122354954, value=sku123

复制

2.值查询

谁的值=sku188

scan 'test1', FILTER="ValueFilter(=,'binary:sku188')"

ROW COLUMN+CELL

user1|ts2 column=sf:c1, timestamp=1409122354918, value=sku188

复制

谁的值包含88

scan 'test1', FILTER="ValueFilter(=,'substring:88')"

ROW COLUMN+CELL

user1|ts2 column=sf:c1, timestamp=1409122354918, value=sku188

user2|ts5 column=sf:c2, timestamp=1409122355030, value=sku288

复制

值小于等于20000

scan 'test1', FILTER="ValueFilter(=,'binary:20000')"

ROW COLUMN+CELL

user3|ts7 column=lf:c1, timestamp=1554866187587, value=12345

复制

注意:如果查询值大于20000,会查出所有值,因为“sku188”等值转为二进制后都大于20000。

substring不能使用小于等于等符号。

3.列查询

column为c2,值包含88的用户

scan 'test1', FILTER="ColumnPrefixFilter('c2') AND ValueFilter(=,'substring:88')"

ROW COLUMN+CELL

user2|ts5 column=sf:c2, timestamp=1409122355030, value=sku288

复制

通过搜索进来的(column为s)值包含123或者222的用户

scan 'test1', FILTER="ColumnPrefixFilter('s') AND ( ValueFilter(=,'substring:123') OR ValueFilter(=,'substring:222') )"

ROW COLUMN+CELL

user1|ts3 column=sf:s1, timestamp=1409122354954, value=sku123

user2|ts6 column=sf:s1, timestamp=1409122355970, value=sku222

复制

列族查询

scan 'test1', FILTER="FamilyFilter(=,'substring:lf')"

ROW COLUMN+CELL

user3|ts7 column=lf:c1, timestamp=1554866187587, value=12345

user3|ts8 column=lf:c1, timestamp=1554866294485, value=67890

复制

4.时间戳

scan 'test1',{FILTER="TimestampsFilter(1448069941270,1548069941230)" }

复制

3.java查询

过滤器

HBase 的基本 API,包括增、删、改、查等。

增、删都是相对简单的操作,与传统的 RDBMS 相比,这里的查询操作略显苍白,只能根据特性的行键进行查询(Get)或者根据行键的范围来查询(Scan)。

HBase 不仅提供了这些简单的查询,而且提供了更加高级的过滤器(Filter)来查询。

过滤器的两类参数

过滤器可以根据列族、列、版本等更多的条件来对数据进行过滤,基于 HBase 本身提供的三维有序(行键,列,版本有序),这些过滤器可以高效地完成查询过滤的任务,带有过滤器条件的 RPC 查询请求会把过滤器分发到各个 RegionServer(这是一个服务端过滤器),这样也可以降低网络传输的压力。

使用过滤器至少需要两类参数:

一类是抽象的操作符

HBase 提供了枚举类型的变量来表示这些抽象的操作符:

LESS

LESS_OR_EQUAL

EQUAL

NOT_EQUAL

GREATER_OR_EQUAL

GREATER

NO_OP

另一类是比较器

代表具体的逻辑,例如字节级的比较,字符串级的比较等。

参数基础

有两个参数类在各类Filter中经常出现,统一介绍下:

(1)比较运算符 CompareFilter.CompareOp

比较运算符用于定义比较关系,可以有以下几类值供选择:

EQUAL 相等

GREATER 大于

GREATER_OR_EQUAL 大于等于

LESS 小于

LESS_OR_EQUAL 小于等于

NOT_EQUAL 不等于

(2)比较器 ByteArrayComparable

通过比较器可以实现多样化目标匹配效果,比较器有以下子类可以使用:

BinaryComparator 匹配完整字节数组

BinaryPrefixComparator 匹配字节数组前缀

BitComparator

NullComparator

RegexStringComparator 正则表达式匹配

SubstringComparator 子串匹配

1,FilterList

FilterList 代表一个过滤器链,它可以包含一组即将应用于目标数据集的过滤器,过滤器间具有“与” FilterList.Operator.MUST_PASS_ALL 和“或” FilterList.Operator.MUST_PASS_ONE 关系。

官网实例代码,两个“或”关系的过滤器的写法:

FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ONE); //数据只要满足一组过滤器中的一个就可以

SingleColumnValueFilter filter1 = new SingleColumnValueFilter(cf,column,CompareOp.EQUAL,Bytes.toBytes("my value"));

list.add(filter1);

SingleColumnValueFilter filter2 = new SingleColumnValueFilter(cf,column,CompareOp.EQUAL,Bytes.toBytes("my other value"));

list.add(filter2);

Scan scan = new Scan();

scan.setFilter(list);

复制

2,列值过滤器--SingleColumnValueFilter

SingleColumnValueFilter 用于测试列值相等 (CompareOp.EQUAL ), 不等 (CompareOp.NOT_EQUAL),或单侧范围 (e.g., CompareOp.GREATER)。

构造函数:

(1)比较的关键字是一个字符数组

SingleColumnValueFilter(byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value)

(2)比较的关键字是一个比较器(比较器下一小节做介绍)

SingleColumnValueFilter(byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, ByteArrayComparable comparator)

注意:根据列的值来决定这一行数据是否返回,落脚点在行,而不是列。我们可以设置filter.setFilterIfMissing(true);如果为true,当这一列不存在时,不会返回,如果为false,当这一列不存在时,会返回所有的列信息

测试表user内容如下:

Table table = connection.getTable(TableName.valueOf("user"));

SingleColumnValueFilter scvf= new SingleColumnValueFilter(Bytes.toBytes("account"), Bytes.toBytes("name"),

CompareOp.EQUAL,"zhangsan".getBytes());

scvf.setFilterIfMissing(true); //默认为false, 没有此列的数据也会返回 ,为true则只返回name=lisi的数据

Scan scan = new Scan();

scan.setFilter(scvf);

ResultScanner resultScanner = table.getScanner(scan);

for (Result result : resultScanner) {

ListCell cells= result.listCells();

for (Cell cell : cells) {

String row = Bytes.toString(result.getRow());

String family1 = Bytes.toString(CellUtil.cloneFamily(cell));

String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));

String value = Bytes.toString(CellUtil.cloneValue(cell));

System.out.println("[row:"+row+"],[family:"+family1+"],[qualifier:"+qualifier+"]"+ ",[value:"+value+"],[time:"+cell.getTimestamp()+"]");

}

}

复制

如果setFilterIfMissing(true), 有匹配只会返回当前列所在的行数据,基于行的数据 country 也返回了,因为他么你的rowkey是相同的

[row:zhangsan_1495527850824],[family:account],[qualifier:country],[value:china],[time:1495636452285]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

复制

如果setFilterIfMissing(false),有匹配的列的值相同会返回,没有此列的 name的也会返回,, 不匹配的name则不会返回。

下面 红色是匹配列内容的会返回,其他的不是account:name列也会返回,, name=lisi的不会返回,因为不匹配。

[row:lisi_1495527849910],[family:account],[qualifier:idcard],[value:42963319861234561230],[time:1495556647872]

[row:lisi_1495527850111],[family:account],[qualifier:password],[value:123451231236],[time:1495556648013]

[row:lisi_1495527850114],[family:address],[qualifier:city],[value:黄埔],[time:1495556648017]

[row:lisi_1495527850136],[family:address],[qualifier:province],[value:shanghai],[time:1495556648041]

[row:lisi_1495527850144],[family:info],[qualifier:age],[value:21],[time:1495556648045]

[row:lisi_1495527850154],[family:info],[qualifier:sex],[value:女],[time:1495556648056]

[row:lisi_1495527850159],[family:userid],[qualifier:id],[value:002],[time:1495556648060]

[row:wangwu_1495595824517],[family:userid],[qualifier:id],[value:009],[time:1495624624131]

[row:zhangsan_1495527850759],[family:account],[qualifier:idcard],[value:9897645464646],[time:1495556648664]

[row:zhangsan_1495527850759],[family:account],[qualifier:passport],[value:5689879898],[time:1495636370056]

[row:zhangsan_1495527850824],[family:account],[qualifier:country],[value:china],[time:1495636452285]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

[row:zhangsan_1495527850951],[family:address],[qualifier:province],[value:guangdong],[time:1495556648855]

[row:zhangsan_1495527850975],[family:info],[qualifier:age],[value:100],[time:1495556648878]

[row:zhangsan_1495527851080],[family:info],[qualifier:sex],[value:男],[time:1495556648983]

[row:zhangsan_1495527851095],[family:userid],[qualifier:id],[value:001],[time:1495556648996]

复制

3 键值元数据

由于HBase 采用键值对保存内部数据,键值元数据过滤器评估一行的键(ColumnFamily:Qualifiers)是否存在

3.1. 基于列族过滤数据的FamilyFilter

构造函数:

FamilyFilter(CompareFilter.CompareOp familyCompareOp, ByteArrayComparable familyComparator)

代码如下:

public static ResultScanner getDataFamilyFilter(String tableName,String family) throws IOException{

Table table = connection.getTable(TableName.valueOf("user"));

FamilyFilter ff = new FamilyFilter(CompareOp.EQUAL ,

new BinaryComparator(Bytes.toBytes("account"))); //表中不存在account列族,过滤结果为空

// new BinaryPrefixComparator(value) //匹配字节数组前缀

// new RegexStringComparator(expr) // 正则表达式匹配

// new SubstringComparator(substr)// 子字符串匹配

Scan scan = new Scan();

// 通过scan.addFamily(family) 也可以实现此操作

scan.setFilter(ff);

ResultScanner resultScanner = table.getScanner(scan);

return resultScanner;

}

复制

测试结果:查询的都是account列簇的内容

[row:lisi_1495527849910],[family:account],[qualifier:idcard],[value:42963319861234561230],[time:1495556647872]

[row:lisi_1495527850081],[family:account],[qualifier:name],[value:lisi],[time:1495556647984]

[row:lisi_1495527850111],[family:account],[qualifier:password],[value:123451231236],[time:1495556648013]

[row:zhangsan_1495527850759],[family:account],[qualifier:idcard],[value:9897645464646],[time:1495556648664]

[row:zhangsan_1495527850759],[family:account],[qualifier:passport],[value:5689879898],[time:1495636370056]

[row:zhangsan_1495527850824],[family:account],[qualifier:country],[value:china],[time:1495636452285]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

复制

3.2. 基于限定符Qualifier(列)过滤数据的QualifierFilter

构造函数:

QualifierFilter(CompareFilter.CompareOp op, ByteArrayComparable qualifierComparator)

Table table = connection.getTable(TableName.valueOf("user"));

QualifierFilter ff = new QualifierFilter(

CompareOp.EQUAL , new BinaryComparator(Bytes.toBytes("name")));

// new BinaryPrefixComparator(value) //匹配字节数组前缀

// new RegexStringComparator(expr) // 正则表达式匹配

// new SubstringComparator(substr)// 子字符串匹配

Scan scan = new Scan();

// 通过scan.addFamily(family) 也可以实现此操作

scan.setFilter(ff);

ResultScanner resultScanner = table.getScanner(scan);

复制

测试结果:只返回 name 的列内容

[row:lisi_1495527850081],[family:account],[qualifier:name],[value:lisi],[time:1495556647984]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

复制

3.3. 基于列名(即Qualifier)前缀过滤数据的ColumnPrefixFilter

( 该功能用QualifierFilter也能实现 )

构造函数:

ColumnPrefixFilter(byte[] prefix)

Table table = connection.getTable(TableName.valueOf("user"));

ColumnPrefixFilter ff = new ColumnPrefixFilter(Bytes.toBytes("name"));

Scan scan = new Scan();

// 通过QualifierFilter的 newBinaryPrefixComparator也可以实现

scan.setFilter(ff);

ResultScanner resultScanner = table.getScanner(scan);

复制

返回结果:

[row:lisi_1495527850081],[family:account],[qualifier:name],[value:lisi],[time:1495556647984]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

复制

3.4. 基于多个列名(即Qualifier)前缀过滤数据的MultipleColumnPrefixFilter

MultipleColumnPrefixFilter 和 ColumnPrefixFilter 行为差不多,但可以指定多个前缀

byte[][] prefixes = new byte[][] {Bytes.toBytes("name"), Bytes.toBytes("age")};

//返回所有行中以name或者age打头的列的数据

MultipleColumnPrefixFilter ff = new MultipleColumnPrefixFilter(prefixes);

Scan scan = new Scan();

scan.setFilter(ff);

ResultScanner rs = table.getScanner(scan);

复制

结果:

[row:lisi_1495527850081],[family:account],[qualifier:name],[value:lisi],[time:1495556647984]

[row:lisi_1495527850144],[family:info],[qualifier:age],[value:21],[time:1495556648045]

[row:zhangsan_1495527850824],[family:account],[qualifier:name],[value:zhangsan],[time:1495556648729]

[row:zhangsan_1495527850975],[family:info],[qualifier:age],[value:100],[time:1495556648878]

复制

3.5. 基于列范围过滤数据ColumnRangeFilter

构造函数:

ColumnRangeFilter(byte[] minColumn, boolean minColumnInclusive, byte[] maxColumn, boolean maxColumnInclusive)

参数解释:

minColumn - 列范围的最小值,如果为空,则没有下限;

minColumnInclusive - 列范围是否包含minColumn ;

maxColumn - 列范围最大值,如果为空,则没有上限;

maxColumnInclusive - 列范围是否包含maxColumn 。

代码:

Table table = connection.getTable(TableName.valueOf("user"));

byte[] startColumn = Bytes.toBytes("a");

byte[] endColumn = Bytes.toBytes("d");

//返回所有列中从a到d打头的范围的数据,

ColumnRangeFilter ff = new ColumnRangeFilter(startColumn, true, endColumn, true);

Scan scan = new Scan();

scan.setFilter(ff);

ResultScanner rs = table.getScanner(scan);

复制

结果:返回列名开头是a 到 d的所有列数据

[row:lisi_1495527850114],[family:address],[qualifier:city],[value:黄埔],[time:1495556648017]

[row:lisi_1495527850144],[family:info],[qualifier:age],[value:21],[time:1495556648045]

[row:zhangsan_1495527850824],[family:account],[qualifier:country],[value:china],[time:1495636452285]

[row:zhangsan_1495527850975

[img]

描述hbase的scan和get功能以及实现的异同

HBase的查询实现只提供两种方式: 1、按指定RowKey获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get) 2、历祥余按指定的条件获宴或取一批记录,scan方法(org.apache.hadoop.hbase.client.Scan) 实肢滚现条件查询功能使用的就是scan方式

HBase中的Scan操作

Lars

Hofhansl 在 HBASE-5268 提出一个"prefix

delete marker"的建议,大概的思想是

如果数据如下:

row column family:qualifier value

r1 cf1:101 XX

r1 cf1:102 XX

r1 cf1:103 XX

r1 cf1:201 XX

r1 cf1:202 XX

如果我们想删除qualifier 101~103的数据,那么在当前hbase中只能一个接一个删除,即打入三个delete

marker。Lars想引入一个前缀删除机制,即删除某个family下面所有以XX开头的qualifier,这样有一个比较明显的好处就是只需要加肆雀岁一次delete

marker,在一些inner row很多的schema下,要进行range

delete时,这样节省的开销还是很大的。然而裂睁这个fix和get的一些逻辑有一定的冲突,后来并未引入到新版本中,Lars也写了一篇博客解释了原因。结合这篇博客和 的comments

list可以对hbase的scan和delete机制有进一步的了解。

在HBase的Delete操作一文中,已经对HBase的删除做了介绍,文中有一点没有提到就是delete marker的位置。column delete marker和他们影响的KV对保存在一起,而family delete marker永远岁档置顶。

hbase(main):001:0 scan 'x2', {RAW=true, VERSIONS=10}

ROW COLUMN+CELL

r1 column=f:c, timestamp=1323323611106, value=v3

r1 column=f:c, timestamp=1323323609988, type=DeleteColumn

r1 column=f:c, timestamp=1323323609988, value=v2

r1 column=f:c, timestamp=1323323608554, value=v1

r2 column=f:c, timestamp=1323323617759, value=v3

r2 column=f:c, timestamp=1323323616226, value=v2

r2 column=f:c, timestamp=1323323614496, value=v1

2 row(s) in 0.6380 seconds

上图中,r1对f:c的删除标记是和kv排在一起的,按照timestamp时间戳的先后排序

hbase(main):005:0 scan 'x1', {RAW=true, VERSIONS=10}

ROW COLUMN+CELL

r2 column=f:, timestamp=1323323616226, type=DeleteFamily

r2 column=f:c, timestamp=1323323617759, value=v3

r2 column=f:c, timestamp=1323323616226, value=v2

r2 column=f:c, timestamp=1323323614496, value=v1

2 row(s) in 0.0500 seconds

上图中,删除column family f的操作是排在最前面的,尽管从时间顺序上它是发生在v2之后,插入v3之前。

在HBase的存储结构中,每个Column family对应的是一个Store,数据存储在数个Storefile中。Scan在Hbase中类似于由RegionScanner进行的MergeSort,由StoreFileScanner,StoreScanner和RegionScanner将结果一级一级汇总。

当我们进行如下一系列操作时:

put: row1, family, col1, value1, T

delete family: row1, family, T+1

put: row1, family, col1, value2, T+2

delete columns: row1, family, col1, T+3

put: row1, family, col1, value3, T+4

实际上,存下来的数据格式类似于

family-delete row1, T+1

row1,col1,value3, T+4

column-delete row1,col1, T+3

row1,col1,value2, T+2

row1,col1,value1, T

family delete marker在最前面是因为它会影响到很多行数据,所以Hbase进行了优化,让Scanner一开始就可以知道它,然后继续向下扫。这就带来了如下结果:

就算我们想找到一个特定的Qualifier对应的Value,我们也需要先seek到这行的开始来看看是否有family delete marker,他们的时间戳是否大于等于我们感兴趣的那个qualifier-value的version。

Lars对Prefix delete marker一开始的设计是让它处于kv对之间,如同column delete marker一样,但是这样的设计会带来如下问题:

一个row或者一个Qualifier的开始是一个定点,然而一个Qualifier

prefix不一定。如当前qualifier有1013,102,103,我们可以认为Qualifier

prefix为10的点在1013前面,然而如果我们加入一个新的Qualifier1012,那么这个点就要在1012前面,为了确定某个qualifier是否被删掉,scanner必须扫描所有可能影响到他的prefix

marker,而这个很可能需要进行全表扫描,开销太大。

然后Lars改变了prefix delete

marker的位置,把他等同于family delete

marker,也就是在行内置顶,这样做可以work,不过会有一些潜在的问题。因为对于每一个row,delete

family的次数不会太多,因为一个storefile只有一个column

family,所以对于scanner来说,它只需要记住这个family delete发生的时间戳,而prefix

delete可能会很多,组合也会比较复杂,如果每扫到一行KV,都要对delete

marker集合进行判断,scan开销就会较大,也就达不到一开始设计的初衷了。于是最后他们决定won‘t

fix,不过patch做好了,只是不会加入新版本中。如果业务中有这样的需求:需要对某些具有共同前缀的qualifier进行删除,然而这种删除操作不太频繁(每两次major

compaction之间这样的操作在少数几次),那么可以考虑加入这个patch,这样可以优化下存储和删除的效率。

PS:在jira里面,他们讨论的还是很热烈的,差不多一天内lars改了6版,真勤奋啊~

关于hbasescan和hbase的scan用法的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

标签列表