本文介绍如何按时间戳划分冷热数据。
背景信息
按时间戳冷热分离是以时间戳为条件,冷热分离时间点为依据,对数据进行归档。未设置自定义时间戳时,默认时间戳的值为数据写入时间。
前提条件
已开通冷存储功能。如何开通,请参见冷存储介绍。
已连接宽表引擎。
如何通过HBase Shell连接宽表引擎,请参见通过Lindorm Shell访问宽表引擎。
如何通过HBase Java API连接宽表引擎,请参见基于HBase Java API的应用开发。
如何通过Lindorm-cli连接宽表引擎,请参见通过Lindorm-cli连接并使用宽表引擎。
表或列簇的存储属性STORAGE_POLICY不能为
COLD
(即需要开启冷热分离的表不能是冷存表)。如何修改存储属性,请参见配置冷存储。
操作步骤
方式一:HBase Shell
创建表并设置冷热分界线。
HBase(main):002:0> create 'chsTable', {NAME=>'f', COLD_BOUNDARY=>'86400'}
参数说明
NAME:需要冷热分离的列簇。
COLD_BOUNDARY:冷热分离时间点,单位为秒(s)。例如COLD_BOUNDARY为86400,代表86400秒(一天)前写入的数据会被自动归档到冷存储。
可选:修改冷热分离分界线。
HBase(main):005:0> alter 'chsTable', {NAME=>'f', COLD_BOUNDARY=>'42300'}
可选:取消冷热分离。
HBase(main):004:0> alter 'chsTable', {NAME=>'f', COLD_BOUNDARY=>""}
说明修改冷热分界线或取消冷热分离后,需要等待系统后台执行完
compaction
,数据才能从冷存储回到热存储中。如果需要数据立即回到热存储,请手动执行major_compact命令。
方式二:HBase Java API
创建表并设置冷热分界线。
Admin admin = connection.getAdmin(); TableName tableName = TableName.valueOf("chsTable"); HTableDescriptor descriptor = new HTableDescriptor(tableName); HColumnDescriptor cf = new HColumnDescriptor("f"); cf.setValue(AliHBaseConstants.COLD_BOUNDARY, "86400"); descriptor.addFamily(cf); admin.createTable(descriptor);
可选:修改冷热分界线。
HTableDescriptor descriptor = admin .getTableDescriptor(tableName); HColumnDescriptor cf = descriptor.getFamily("f".getBytes()); cf.setValue(AliHBaseConstants.COLD_BOUNDARY, "86400"); admin.modifyTable(tableName, descriptor);
可选:取消冷热分离。
HTableDescriptor descriptor = admin .getTableDescriptor(tableName); HColumnDescriptor cf = descriptor.getFamily("f".getBytes()); cf.setValue(AliHBaseConstants.COLD_BOUNDARY, null); admin.modifyTable(tableName, descriptor);
说明修改冷热分界线或取消冷热分离后,需要等待系统后台执行完
compaction
后,数据才能从冷存储回到热存储中。
方式三:lindorm-cli
设置表的冷热分界线。
方法一:建表时开启冷热分离并设置冷热分界线。
CREATE TABLE dt ( p1 integer, p2 integer, c1 varchar, c2 bigint, constraint pk primary key(p1 desc)) WITH (COMPRESSION = 'ZSTD', CHS = '86400', CHS_L2 = 'storagetype=COLD');
参数说明
CHS:冷热分离时间点,单位为秒(s)。例如CHS为86400,代表自定义时间列的时间戳取值在86400秒(一天)前的数据会被自动归档到冷存储。
COMPRESSION:压缩算法。整张表生效。算法名称大小写不敏感。默认配置为none。
CHS_L2:配置第二层属性,一般配置存储属性:storagetype=COLD。
方法二:建表时未设置,通过
ALTER TABLE
语句添加相关属性。-- 已有数据表且建表时未开启冷热分离,例如: -- CREATE TABLE dt (p1 integer, p2 integer, c1 varchar, c2 bigint, constraint pk primary key(p1 desc)) WITH (COMPRESSION = 'ZSTD'); -- 为表dt开启冷热分离功能 ALTER TABLE dt SET 'CHS' ='86400', 'CHS_L2' = 'storagetype=COLD';
可选:修改冷热分界线。
ALTER TABLE dt SET 'CHS'='1000';
可选:取消冷热分离。
ALTER TABLE dt SET 'CHS'='', 'CHS_L2' = '';
说明修改冷热分界线或取消冷热分离后,需要等待系统后台执行完成
compaction
,数据才能从冷存储回到热存储中。如果需要数据立即回到热存储,请手动执行major_compact
。具体语法,请参见ALTER TABLE。
数据写入
冷热分离的表与普通表的数据写入方式完全一致,时间戳默认为数据写入时的当前系统时间。您也可以在使用HBase接口写入数据时自定义时间戳。新写入的数据会先存储在热存储(标准型/性能型)中,随着时间的推移,如果当前时间-写入时间>COLD_BOUNDARY/CHS设置的值
,则会在系统后台执行compaction
时被归档到冷存储中。
自定义时间戳可能会影响数据的冷热判断。系统会将当前时间与自定义时间戳进行比较,假设冷热分离线为3天,自定义时间戳为系统当前时间3天后的某一时间点,则写入的数据会比当前写入时间延迟3天才能进入冷存储;假设冷热分离时间点为3天,自定义时间戳为3天前的某一时间点,则新写入的数据将会通过异步归档进入冷存储。
数据查询
由于冷热数据都在同一张表中,因此所有的查询操作都只需在一张表内进行。在查询时,可以通过调整TimeRange参数来设置查询的时间范围,系统将会根据指定的时间范围决定查询方式,如仅查询热存储区、仅查询冷存储区或同时查询冷存储区和热存储区。如果查询时未指定时间范围,系统可能会查询到冷数据,进而使得查询吞吐量受到冷存储的限制。详细内容,请参见冷存储介绍。
如果已确定需要查询的数据为热数据,也可以使用HINT设置_l_hot_only_或HOT_ONLY参数的属性来避免查询冷数据。
冷热分离表中的冷存储仅用于归档数据,查询请求很少。如果有大量请求需要去查询冷数据,则需要考虑当前COLD_BOUNDARY设置的值是否会导致系统查询的冷数据过多进而影响查询效率。
如果一行数据已经在冷存储,但这行数据后续有更新,更新的字段会先保存在热存储。如果此时设置HOT_ONLY或TimeRange只查询热存储中的数据,则只会返回这一行存储在热存储的字段。只有在查询时去掉HOT_ONLY和TimeRange,或保证TimeRange覆盖了该行数据的插入和更新时间,才能完整返回这一行数据。因此不建议更新进入冷存储区的数据。
目前支持随机查询Get和范围查询Scan两种查询方式。
随机查询Get
方式一:HBase Shell
不带HOT_ONLY的查询,可能会查询到冷数据。
HBase(main):013:0> get 'chsTable', 'row1'
带HOT_ONLY的查询,只查询热数据。
HBase(main):015:0> get 'chsTable', 'row1', {HOT_ONLY=>true}
带TimeRange的查询,系统会通过比较TimeRange与COLD_BOUNDARY来决定查询方式,即仅查询冷数据、仅查询热数据或冷热数据都查询。
HBase(main):016:0> get 'chsTable', 'row1', {TIMERANGE => [0, 1568203111265]}
参数说明
TimeRange:时间范围。格式为Unix时间戳,单位为毫秒(ms)。
方式二:HBase Java API
不带HOT_ONLY的查询,可能会查询到冷数据。
Get get = new Get("row1".getBytes()); System.out.println("result: " + table.get(get));
带HOT_ONLY的查询,只查询热数据。
get = new Get("row1".getBytes()); get.setAttribute(AliHBaseConstants.HOT_ONLY, Bytes.toBytes(true));
带TimeRange的查询,系统会根据设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据。
get = new Get("row1".getBytes()); get.setTimeRange(0, 1568203111265)
方式三:Lindorm SQL
SELECT /*+ _l_hot_only_ */ * FROM dt WHERE pk IN (1, 2, 3);
范围查询Scan
如果范围查询(Scan)不设置HOT_ONLY参数和TimeRange,或TimeRange包含位于冷存储区的时间,则系统会并行查询冷热数据并合并结果。
范围查询仅支持HBase Shell和HBase Java API使用方式。
方式一:HBase Shell
不带HOT_ONLY的查询,一定会查询到冷数据。
Lindorm(main):017:0> scan 'chsTable', {STARTROW =>'row1', STOPROW=>'row9'}
带HOT_ONLY的查询,只查询热数据。
Lindorm(main):018:0> scan 'chsTable', {STARTROW =>'row1', STOPROW=>'row9', HOT_ONLY=>true}
带TimeRange的查询,系统会将设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据。
Lindorm(main):019:0> scan 'chsTable', {STARTROW =>'row1', STOPROW=>'row9', TIMERANGE => [0, 1568203111265]}
方式二:HBase Java API
TableName tableName = TableName.valueOf("chsTable"); Table table = connection.getTable(tableName); // 不带Hot_Only的查询,一定会查询到冷数据 Scan scan = new Scan(); ResultScanner scanner = table.getScanner(scan); for (Result result : scanner) { System.out.println("scan result:" + result); } // 带Hot_Only的查询,只会查询热数据部分 scan = new Scan(); scan.setAttribute(AliLindormConstants.HOT_ONLY, Bytes.toBytes(true)); // 带TimeRange的查询,系统会通过比较TimeRange与COLD_BOUNDARY来决定查询方式 scan = new Scan(); scan.setTimeRange(0, 1568203111265);
优先查询热数据
在Scan场景下,查询的数据可能既有冷数据又有热数据,例如查询一个用户的所有订单、聊天记录等。查询的结果通常以分页的形式从新到旧展示,最先展示的往往是最近的热数据。在此场景下,普通不带Hot_Only的Scan会并行扫描冷热数据,导致请求性能下降。而在开启了优先查询热数据后,会优先只查热数据。只有热数据的数量未达到指定的Limit数量时,系统才会去查询冷数据。减少冷存储的访问,可提升请求响应。
优先查询热数据功能仅支持HBase Shell和HBase Java API使用方式,不支持Lindorm SQL。
方式一:HBase Shell
Lindorm(main):002:0> scan 'chsTable', {COLD_HOT_MERGE=>true}
参数说明
COLD_HOT_MERGE:是否开启优先查询热数据。
true:是。优先查询热存储中的数据,若热存储中的数据查完了,用户仍然在调用获取下一条数据,则会开始查询冷数据。
false:否。不开启优先查询热数据功能。
方式二:HBase Java API
scan = new Scan(); scan.setAttribute(AliHBaseConstants.COLD_HOT_MERGE, Bytes.toBytes(true)); scanner = table.getScanner(scan);
注意事项
若某一行数据同时包含热数据和冷数据,例如更新了部分列导致一行数据中既存在热数据又存在冷数据的场景,开启查询热数据优先功能会导致该行的查询结果分两次返回,即Scanner返回的Result集合中,对于同一个Rowkey会有两个对应的Result。
开启热数据优先功能后,由于是先返回热数据,再返回冷数据。因此,无法保证后返回的冷数据结果的Rowkey一定大于先返回的热数据结果的Rowkey,即Scan得到的Result集合不保序, 但热数据和冷数据的各自返回集合仍保证按Rowkey排序,结果如下HBase Shell示例所示。在实际使用时, 您可以通过Rowkey设计,保障Scan的结果仍然保序,比如订单记录表,Rowkey为用户ID和订单创建时间,扫描某个用户的订单数据是有序的。
//假设Rowkey为"coldRow"的这一行是冷数据,Rowkey为"hotRow"的这一行为热数据 //正常情况下,由于Lindorm的row是字典序排列,Rowkey为"coldRow"的这一行会比"hotRow"这一行先返回。 HBase(main):001:0> scan 'chsTable' ROW COLUMN+CELL coldRow column=f:value, timestamp=1560578400000, value=cold_value hotRow column=f:value, timestamp=1565848800000, value=hot_value 2 row(s) // 设置COLD_HOT_MERGE时, scan的Rowkey顺序被破坏,热数据比冷数据先返回,因此返回的结果中,"hot"排在了"cold"的前面 HBase(main):002:0> scan 'chsTable', {COLD_HOT_MERGE=>true} ROW COLUMN+CELL hotRow column=f:value, timestamp=1565848800000, value=hot_value coldRow column=f:value, timestamp=1560578400000, value=cold_value 2 row(s)
常见问题
Q:冷数据更新后还是冷数据吗?
A:不是。由于更新后的数据重新记录了时间戳,因此冷数据更新后变为热数据。
Q:我只想查询热数据,为什么还返回了冷数据?
A:查询语句可以通过设置HOT_ONLY/_l_hot_only_仅查询热数据。但由于数据归档至冷存储的操作是周期性触发的,因此有部分冷数据可能会滞留在热存储,导致查询结果中包含了冷数据。可以在查询条件中添加热数据时间范围,避免出现此类问题。示例如下:
// 您需要设置_l_ts_min_(当前时间-冷热分界线), _l_ts_max_(当前时间), 注意时间单位统一 SELECT /*+ _l_hot_only_(true), _l_ts_min_(1000), _l_ts_max_(2001) */ * FROM test WHERE p1>1;
Q:已设置时间范围,也使用了HOT_ONLY,为什么查询热数据超时了?
A:这种情况一般发生在数据迁移或者修改表属性为冷热分离表后。此时系统尚未触发数据归档至冷存储,因此大量的冷数据滞留于热存储中,数据量的增大导致了查询效率的降低。需要对表执行
major compaction
操作来解决这一问题。具体语法,请参见ALTER TABLE。