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

背景

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

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

详细介绍请参见云栖社区文章——《面向海量数据的极致成本优化-云Lindorm的一体化冷热分离

原理介绍

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

使用准备

  • 开通冷存储功能

    请参见使用冷存储中的指导开通集群的冷存储功能。

  • 升级服务端版本
    说明 冷存储功能需要Lindorm服务端升级到2.1.8版本以上
  • 安装SDK和Shell

    通过HBase shell使用该功能,请参见使用HBase Shell访问Lindorm文档中的步骤完成Shell的下载和配置。

    通过HBase java api使用该功能,请参见使用HBase Java API 访问Lindorm文档中的步骤完成Java SDK安装和参数配置。

    通过Cassandra cqlsh使用该功能,请参见使用cqlsh访问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

    待补充

基础使用(二):数据写入

冷热分离的表与普通表的数据写入方式完全一致,时间戳默认为数据写入时的系统当前时间。数据先会存储在热存储(标准型/性能型)中。随着时间的推移,如果这行数据的写入时间超过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
  • 范围查询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);
    • 方式三:Cassandra CQL

      待补充

    说明
    • 冷热分离表中的冷区只是用来归档数据,查询请求应该非常的少,用户查询冷热分离表的绝大部分请求应该带上HOT_ONLY的标记(或者设置的TimeRange只在热区)。如果用户有大量请求需要去查冷区数据,则可能得考虑COLD_BOUNDARY冷热分界线的设置是否合理。
    • 如果一行数据已经在冷数据区域,但这一行后续有更新,更新的字段先会在热区,如果设置HOT_ONLY去查询这一行(或者设置的TimeRange只在热区),则只会返回这一行更新的字段(在热区)。只有在查询时去掉HOT_ONLY Hint,去掉TimeRange,或保证TimeRange覆盖了该行数据插入和更新时间,才能完整返回这一行。因此不建议对已经进入冷区的数据进行更新,如果有频繁更新冷数据的需求,则可能得考虑COLD_BOUNDARY冷热分界线的设置是否合理。

基础使用(四):查看冷热数据的大小

集群管理系统的表Tab中,可以显示某一张表的冷存储使用大小和热存储使用大小。如果数据还没有进入冷存储,有可能数据还在内存中,请执行flush,将数据刷写到盘上,再请执行major_compact完成后再查看。

基础使用(五):优先查询热数据

在范围查询(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);
  • 方式三:Cassandra CQL

    待补充

说明
  • 若某一行同时包含热数据和冷数据(部分列属于热数据,部分列属于冷数据,比如部分列更新场景),开启热数据优先功能,会使得该行的查询结果会分两次返回,即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)

高级使用:自定义划分数据的冷热

除了通过写入KV的时间戳区分冷热,Lindorm还支持通过Rowkey中的某些字段来区分数据的冷热,如用户的Rowkey中包含一个timestamp,Lindorm支持parse这个timestamp来划分这行数据的冷热,而不是根据KeyValue的写入时间戳。如果根据KV写入时间戳划分冷热无法满足用户需求,欢迎提工单或者钉钉上通过专家服务来询问使用方法。

注意事项

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