Lindorm在同一张表里实现了数据的冷热分离,系统会自动根据用户设置的冷热分界线自动将表中的冷、热数据分类归档到冷、热存储中。

背景

在海量大数据场景下,一张表中的部分业务数据随着时间的推移仅作为归档数据或者访问频率很低,同时这部分历史数据体量非常大,比如订单数据或者监控数据,降低这部分数据的存储成本将会极大的节省企业的成本。如何以极简的运维配置成本就能为企业极大降低存储成本,阿里云Lindorm冷热分离功能应运而生,其支持冷热数据使用不同介质存储,冷存储价格仅为标准型存储(存储热数据)的20%。

Lindorm在同一张表里实现了数据的冷热分离,系统会自动根据用户设置的冷热分界线自动将表中的冷数据归档到冷存储中。在用户的访问方式上和普通表几乎没有任何差异,在查询的过程中,用户只需配置查询Hint或者TimeRange,系统根据条件自动地判断查询应该落在热数据区还是冷数据区。对用户而言始终是一张表,对用户几乎做到完全的透明。

原理介绍

用户在表上配置数据冷热时间分界点,Lindorm依赖用户写入数据的时间戳(毫秒)和时间分界点来判断数据的冷热。数据最初始在热存储上,随意时间的推移慢慢往冷数据迁移。同时用户可以任意变更数据的冷热分界点,数据可以从热到冷,也可以从冷到热。

使用准备

表设置冷热分界线

用户在使用过程中可以随时调整COLD_BOUNDARY来划分冷热的边界。COLD_BOUNDARY的单位为秒,例如COLD_BOUNDARY为86400,代表86400秒(一天)前写入的数据会被自动归档到冷存储介质上。

说明 在冷热分离使用过程中,无需把表或列簇的属性设置为COLD,如果已经把列簇的属性设置为了COLD,请依照冷存储介绍文档中的指导将冷存储的属性去除。

具体操作方法,如下:

  • 方式一:HBase Shell
    // 创建冷热分离表
    HBase(main):002:0> create 'chsTable', {NAME=>'f', COLD_BOUNDARY=>'86400'}
    // 取消冷热分离
    HBase(main):004:0> alter 'chsTable', {NAME=>'f', COLD_BOUNDARY=>""}
    // 为已经存在的表设置冷热分离,或者修改冷热分离分界线,单位为秒
    HBase(main):005:0> alter 'chsTable', {NAME=>'f', COLD_BOUNDARY=>'86400'}
  • 方式二:HBase Java API
    // 新建冷热分离表
    Admin admin = connection.getAdmin();
    TableName tableName = TableName.valueOf("chsTable");
    HTableDescriptor descriptor = new HTableDescriptor(tableName);
    HColumnDescriptor cf = new HColumnDescriptor("f");
    // COLD_BOUNDARY 设置冷热分离时间分界点,单位为秒, 示例表示1天之前的数据归档为冷数据
    cf.setValue(AliHBaseConstants.COLD_BOUNDARY, "86400");
    descriptor.addFamily(cf);
    admin.createTable(descriptor);
    
    // 取消冷热分离
    // 注意:需要做major compaction,数据才能从冷存储上回到热存储上
    HTableDescriptor descriptor = admin
        .getTableDescriptor(tableName);
    HColumnDescriptor cf = descriptor.getFamily("f".getBytes());
    // 取消冷热分离
    cf.setValue(AliHBaseConstants.COLD_BOUNDARY, null);
    admin.modifyTable(tableName, descriptor);
    
    // 为已经存在的表设置冷热分离功能,或者修改冷热分离分界线
    HTableDescriptor descriptor = admin
        .getTableDescriptor(tableName);
    HColumnDescriptor cf = descriptor.getFamily("f".getBytes());
    // COLD_BOUNDARY 设置冷热分离时间分界点,单位为秒, 示例表示1天之前的数据归档为冷数据
    cf.setValue(AliHBaseConstants.COLD_BOUNDARY, "86400");
    admin.modifyTable(tableName, descriptor);
  • 方式三:Cassandra CQL

    通过cqlsh可以使用cql设置lindorm的冷热分离属性,方式如下:

    // COLD_BOUNDARY 设置冷热分离时间分界点,单位为秒, 示例表示1天之前的数据归档为冷数据
    create table tb (pk text PRIMARY KEY, c1 text) WITH extensions = {'COLD_BOUNDARY':'86400'}
    //修改存储介质属性以及冷热分离时间线
    alter table tb  with extensions = {'STORAGE_POLICY' : 'DEFAULT', 'COLD_BOUNDARY':'1000'}
                        
  • 方式四:lindorm-cli
    USE test;
    // CHS 冷热分离时间点,单位为秒,示例表示1天之前的数据归档为冷数据,需要同时设置CHS_L2='storagetype=COLD',表明冷数据的存储属性为COLD。 
    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');
    // 修改冷热分离分界线
    ALTER TABLE dt SET 'CHS'='1000';
    
    // 取消冷热分离
    ALTER TABLE dt SET 'CHS'='', 'CHS_L2' = '';
                        

数据写入

冷热分离的表与普通表的数据写入方式完全一致,时间戳默认为数据写入时的系统当前时间。数据先会存储在热存储(标准型/性能型)中。随着时间的推移,如果这行数据的写入时间超过COLD_BOUNDARY设置的值,就会在major_compact时归档到冷数据,此过程完全对用户透明。

数据查询

由于冷热数据都在同一张表中,用户全程只需要和一张表交互。在查询过程中,如果用户明确知道需要查询的数据在热数据里(写入时间少于COLD_BOUNDARY设置的值),可以在查询时设置HOT_ONLY的Hint来告诉服务器只查询热区数据。或者在查询时设置TimeRange来限定查询数据的时间,系统会根据设置TimeRange决定是查询热区、冷区还是冷热都查。查询冷区数据延迟要比热区数据延迟高的多,并且查询吞吐受到冷存储限制(详见冷存储介绍文档)。

查询示例:

  • 随机查询Get
    • 方式一:HBase Shell
      // 不带HotOnly Hint的查询,可能会查询到冷数据
      HBase(main):013:0> get 'chsTable', 'row1'
      // 带HotOnly Hint的查询,只会查热数据部分,如row1是在冷存储中,该查询会没有结果
      HBase(main):015:0> get 'chsTable', 'row1', {HOT_ONLY=>true}
      // 带TimeRange的查询,系统会根据设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据(注意TimeRange的单位为毫秒时间戳)
      HBase(main):016:0> get 'chsTable', 'row1', {TIMERANGE => [0, 1568203111265]}
    • 方式二:HBase Java API
      Table table = connection.getTable("chsTable");
      // 不带HotOnly Hint的查询,可能会查询到冷数据
      Get get = new Get("row1".getBytes());
      System.out.println("result: " + table.get(get));
      // 带HotOnly Hint的查询,只会查热数据部分,如row1是在冷存储中,该查询会没有结果
      get = new Get("row1".getBytes());
      get.setAttribute(AliHBaseConstants.HOT_ONLY, Bytes.toBytes(true));
      // 带TimeRange的查询,系统会根据设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据(注意TimeRange的单位为毫秒时间戳)
      get = new Get("row1".getBytes());
      get.setTimeRange(0, 1568203111265)
    • 方式三:Cassandra CQL
      //关键字hotdata表示读取热数据,暂时cql只支持读取热数据或者全部读
      select hotdata * from tb;
    • 方式四:Lindorm SQL
      SELECT /*+ _l_hot_only_ */ * FROM dt WHERE pk IN (1, 2, 3);
      说明 可以使用HINT设置_l_hot_only_参数的属性。具体操作,请参见通过HINT查询热数据
  • 范围查询Scan
    说明 如果scan不设置Hot Only,或者TimeRange包含冷区时间,则会并行访问冷数据和热数据来合并结果,这是由于Lindorm的Scan原理决定的。
    • 方式一:HBase Shell
      // 不带HotOnly Hint的查询,一定会查询到冷数据
      Lindorm(main):017:0> scan 'chsTable', {STARTROW =>'row1', STOPROW=>'row9'}
      // 带HotOnly Hint的查询,只会查询热数据部分
      Lindorm(main):018:0> scan 'chsTable', {STARTROW =>'row1', STOPROW=>'row9', HOT_ONLY=>true}
      // 带TimeRange的查询,系统会根据设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据(注意TimeRange的单位为毫秒时间戳)
      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);
      // 不带HotOnly Hint的查询,一定会查询到冷数据
      Scan scan = new Scan();
      ResultScanner scanner = table.getScanner(scan);
      for (Result result : scanner) {
          System.out.println("scan result:" + result);
      }
      // 带HotOnly Hint的查询,只会查询热数据部分
      scan = new Scan();
      scan.setAttribute(AliLindormConstants.HOT_ONLY, Bytes.toBytes(true));
      // 带TimeRange的查询,系统会根据设置的TimeRange与COLD_BOUNDARY冷热分界线进行比较来决定查询哪个区域的数据(注意TimeRange的单位为毫秒时间戳)
      scan = new Scan();
      scan.setTimeRange(0, 1568203111265);
    说明
    • 冷热分离表中的冷区只是用来归档数据,查询请求很少,用户查询冷热分离表的绝大部分请求应该设置HOT_ONLY的标记(或者设置的TimeRange只在热区)。如果用户有大量请求需要去查冷区数据,则需要考虑COLD_BOUNDARY冷热分界线的设置是否合理。
    • 如果一行数据已经在冷数据区域,但这一行数据后续有更新,更新的字段会先存储在热区,如果设置HOT_ONLY去查询这一行数据(或者设置的TimeRange只在热区),只会返回这一行存储在热区的字段。只有在查询时去掉HOT_ONLY Hint,去掉TimeRange,或保证TimeRange覆盖了该行数据的插入和更新时间,才能完整返回这一行数据。因此不建议更新进入冷区的数据。如果有频繁更新冷数据的需求,需要考虑COLD_BOUNDARY冷热分界线的设置是否合理。

查看冷热数据的大小

重要 如果数据还没有进入冷存储,有可能数据还在内存中,请执行flush,将数据刷写到盘上,再请执行major_compact完成后再查看。

登录集群管理系统的表Tab中,可以查看某一张表的冷存储使用大小和热存储使用大小。

优先查询热数据

在范围查询(Scan)场景下,查询的数据可能横跨冷热区,比如查询一个用户的所有订单、聊天记录等。但查询的展示往往是从新到旧的分页展示,最先展示的往往是最近的热数据。在这个场景下,普通的Scan(不带Hot_Only)会并行地扫描冷热数据,导致请求性能下降。而在开启了优先查询热数据后,会优先只查热数据,只有热数据的条数不够显示(如用户点了下一页查看),才会去查询冷数据,减少冷存储的访问,提升请求响应。

开启热数据优先查询,只需在Scan上设置COLD_HOT_MERGE属性即可。该属性的含义是优先查询热存储中的数据, 若热存储中的数据查完了,用户仍然在调用获取下一条数据,则会开始查询冷数据。

  • 方式一:HBase Shell
    Lindorm(main):002:0> scan 'chsTable', {COLD_HOT_MERGE=>true}
  • 方式二: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有序(参见下面的demo)。在部分实际场景中, 用户可以通过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)

按列冷热分离

您可以指定一行中的某一列的数据作为这一行冷热分离的边界,而不是依赖数据写入的时间戳,使用方式如下:

USE test;//创建冷热分离的表
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');
//设置列c2为冷热分离的基准列 
ALTER TABLE dt SET 'CHS_COLUMN'='COLUMN=c2'; 
说明
  • 按列冷热分离目前只支持LindormSQL,不支持HBase兼容使用方式。
  • 指定的列需要为Long类型,同时存的值应该为13位的时间戳,如果指定列不存在,数据没有写入,或者写入的数据类型不对,则不进行冷热分离,数据都写入热存。
  • 指定按列做冷热分离,则当这⼀列⾥的值超过了设定的冷热分界线之后,会将整⾏数据归档到冷存。 ⽐如c2⾥存的long 与当前时间差为2天,冷热分界线是1天,c1的时间戳(没有指定则默认为写⼊到系统的时间)与当前时间差为1⼩时,则这⼀⾏都会在major compaction的时候归档到冷存。
  • 按列做冷热分离不适⽤于按冷热边界⾃动归档。只能在major compaction时做数据归档,如您需要调整冷热归档频率,请修改major compaction周期。

Q&A

  • 数据什么时候进冷存?

    Lindorm通过compaction机制异步得将冷数据从热存中归档到冷存,触发时间默认为冷热分界的一半,最小为1天。比如您设置冷热边界是3天,那么默认1.5天就会自动触发一次compaction;如果设置冷热边界为1天,后台会1天触发一次compaction。

  • 可以手动强制触发compaction吗?

    可以的,您可以通过HBase shell对表执行 major_compact 'tableName' 命令来强制触发major compaction,不仅把热存中的冷数据归档到冷存中,同时也会合并所有冷区热区的文件。但请不要频繁使用该命令,它会加重IO负载。

  • 为什么表冷数据归档的慢?

    请检查您的Lindorm版本,如果版本不大于2.1.20,可以升级到最新的版本,冷数据会根据 1中提到的周期进入冷存。如果您选择暂缓升级,可以联系技术支持来帮您优化参数。

注意事项

请参见冷存储介绍的使用注意事项。