面向 Cassandra 用户的 Cloud Bigtable 简介

1. 简介

此 Codelab 是面向将查询从 Apache Cassandra 迁移到 Google Cloud Bigtable 的所有用户的指南。

在此 Codelab 中,您将:

  • 使用 Cloud Bigtable 模拟器
  • 探索时间序列使用场景
  • 创建表和列族
  • 了解 CQL 插入、更新、选择和删除的 Cloud Bigtable Java 等效项

您如何评价自己使用 Google Cloud Platform 的体验?

新手水平 中等水平 熟练水平

您打算如何使用本教程?

<ph type="x-smartling-placeholder"></ph> 仅通读 阅读并完成练习

2. 设置

您将看到一个只包含几行的示例数据集,以便您快速了解核心概念。

Cassandra

每个步骤都会提供等效的 Cassandra 查询,因此您可以根据需要随时在本地集群进行操作,也可以快速设置一键部署的 Cassandra 集群并通过 SSH 连接到该集群。

如果您一直按照这些说明操作,请创建一个键空间并使用它。复制策略在这里并不重要。

cqlsh> create keyspace mykeyspace with replication = {'class':'SimpleStrategy','replication_factor' : 2};
cqlsh> use mykeyspace;

Cloud Bigtable

您需要为表创建一个 Cloud Bigtable 实例。您可以使用模拟器免费设置本地实例。您无需在 Cloud Bigtable 中创建键空间,复制操作由实例配置处理。

使用以下命令启动模拟器:

gcloud beta emulators bigtable start

然后在另一个 shell 窗口或标签页中使用以下命令设置模拟器环境变量:

$(gcloud beta emulators bigtable env-init) #Sets BIGTAB`LE_EMULATOR_HOST

然后创建一个用于运行代码示例的 Java 项目,并使用 Maven、Gradle 或 SBT 导入 Cloud Bigtable 客户端。然后在新的 Java 文件中创建与数据客户端的连接。

BigtableDataSettings settings =
    BigtableDataSettings.newBuilder().setProjectId(projectId).setInstanceId(instanceId).build();

try {
  dataClient = BigtableDataClient.create(settings);
} catch (Exception e) {
  System.out.println("Error during data client connection: \n" + e.toString());
}

3. 表创建

在 Cassandra 和 Cloud Bigtable 表中,每一行都有一个与之关联的键。Cassandra 键具有可以单独或重叠的分区键和聚簇列。Cloud Bigtable 的全部键都用于分块(分区)和排序。在主键/行键构建方面,这两个系统非常相似。这两个系统基本上都是按字典顺序排序的列表,其中键充当节点之间行分布的主要形式。在大多数情况下,您可以对 Cloud Bigtable 重复使用同一键。

在此 Codelab 中,您将使用 Cloud Bigtable 来存储有关手机和平板电脑的时间序列数据(不要与 Bigtable 平板电脑混淆)。以下是有关如何创建表的说明。

Cassandra

cqlsh:mykeyspace> create table mobileTimeSeries (
           deviceid text,
           devicetype text,
           date date,
           connected_cell map<timestamp,Boolean>, 
           os_build text, 
           os_name text,
           PRIMARY KEY((devicetype, deviceid), date));

Cloud Bigtable

您可以使用 Java 客户端创建表和列族,但最简单的方法是将以下命令与 cbt 工具搭配使用:

cbt createtable mobile-time-series families=stats_summary

Cloud Bigtable 是一个 NoSQL 数据库,因此您无需在创建表时定义架构,但您需要考虑将要运行的查询并为其优化行键。

键迁移最常见的策略就是将所有分区键和聚簇列联接起来,形成一个表示 Cloud Bigtable 行键的字符串。我们通常建议使用基于字符串的键,因为它们有助于通过 Key Visualizer 调试键分布。您可以使用井号“#”等分隔符以提高可读性。

在此示例中,我们将使用行键“[DEVICE_TYPE]#[DEVICE_ID]#[YYYYMMDD]”

4. 插入内容

Cassandra 与 Cloud Bigtable 之间的插入非常相似。您将在此处使用行键“[DEVICE_TYPE]#[DEVICE_ID]#[YYYYMMDD]”插入一行,然后使用行键插入多行。

Cassandra

单个

cqlsh:mykeyspace> insert into mobileTimeSeries (deviceid, devicetype, date, connected_cell, os_build) values ('4c410523', 'phone',toDate(now()), {toTimeStamp(now()): true}, 'PQ2A.190405.003');

Batch

cqlsh:mykeyspace> BEGIN BATCH
insert into mobileTimeSeries (deviceid, devicetype, date, os_name, os_build) values ('a0b81f74', 'tablet', '2019-01-01', 'chromeos', '12155.0.0-rc1');
insert into mobileTimeSeries (deviceid, devicetype, date, os_name, os_build) values ('a0b81f74', 'tablet', '2019-01-02','chromeos', '12145.0.0-rc6');
APPLY BATCH;

Cloud Bigtable

单个

使用您要使用的行键和数据创建变更,然后在数据客户端中应用变更。您将为一部手机添加一行数据,其中包含有关手机的移动网络连接和操作系统的信息。

try {
  System.currentTimeMillis();
  long timestamp = (long) 1556712000 * 1000; // Timestamp of June 1, 2019 12:00

  String rowKey = "phone#4c410523#20190501";
  ByteString one = ByteString.copyFrom(new byte[] {0, 0, 0, 0, 0, 0, 0, 1});

  RowMutation rowMutation =
      RowMutation.create(tableId, rowKey)
          .setCell(
              COLUMN_FAMILY_NAME,
              ByteString.copyFrom("connected_cell".getBytes()),
              timestamp,
              one)
          .setCell(COLUMN_FAMILY_NAME, "os_build", timestamp, "PQ2A.190405.003");

  dataClient.mutateRow(rowMutation);
} catch (Exception e) {
  System.out.println("Error during Write: \n" + e.toString());
}

Batch

在 BulkMutation 对象上定义多项更改,然后使用数据客户端通过一次 API 调用应用所有更改。您需要添加几天关于移动设备操作系统名称和版本的数据。

try {
  long timestamp = (long) 1556712000 * 1000; // Timestamp of June 1, 2019 12:00

  BulkMutation bulkMutation =
      BulkMutation.create(tableId)
          .add(
              "tablet#a0b81f74#20190501",
              Mutation.create()
                  .setCell(COLUMN_FAMILY_NAME, "os_name", timestamp, "chromeos")
                  .setCell(COLUMN_FAMILY_NAME, "os_build", timestamp, "12155.0.0-rc1"))
          .add(
              "tablet#a0b81f74#20190502",
              Mutation.create()
                  .setCell(COLUMN_FAMILY_NAME, "os_name", timestamp, "chromeos")
                  .setCell(COLUMN_FAMILY_NAME, "os_build", timestamp, "12155.0.0-rc6"));

  dataClient.bulkMutateRows(bulkMutation);
} catch (Exception e) {
  System.out.println("Error during WriteBatch: \n" + e.toString());
}

5. 最新动态

在这里,您将更新一个尚未写入的单元格,然后在该单元格中写入新值,同时保留之前的版本。

Cassandra

添加单元格

cqlsh:mykeyspace> UPDATE mobileTimeSeries SET os_name = 'android' WHERE devicetype='phone' AND deviceid = '4c410523' AND date = '2019-09-06';

更新单元格

cqlsh:mykeyspace> UPDATE mobileTimeSeries SET connected_cell = connected_cell +  {toTimeStamp(now()): false} WHERE devicetype='phone' AND deviceid = '4c410523' AND date = '2019-09-06';

Cloud Bigtable

在 Cloud Bigtable 中,您可以只将更新视为写入。

添加单元格

这与写入单元格相同,只需提供之前没有写入的列。在这里,您可以将操作系统名称添加到手机所在的行中。

try {
  long timestamp = (long) 1556713800 * 1000; // Timestamp of June 1, 2019 12:30

  String rowKey = "phone#4c410523#20190501";

  RowMutation rowMutation =
      RowMutation.create(tableId, rowKey)
          .setCell(COLUMN_FAMILY_NAME, "os_name", timestamp, "android");

  dataClient.mutateRow(rowMutation);
} catch (Exception e) {
  System.out.println("Error during update: \n" + e.toString());
}

正在更新单元格

您可以在此处添加有关手机移动网络连接状态的新数据。您可以使用单元格版本轻松存储部分时间序列数据。只需为写入操作提供时间戳,即可为单元格添加一个新版本。如需清理数据,您可以使用垃圾回收功能删除一定数量或一段时间后的版本。

try {
  long timestamp = (long) 1556713800 * 1000; // Timestamp of June 1, 2019 12:30

  String rowKey = "phone#4c410523#20190501";

  ByteString zero = ByteString.copyFrom(new byte[] {0, 0, 0, 0, 0, 0, 0, 0});

  RowMutation rowMutation =
      RowMutation.create(tableId, rowKey)
          .setCell(
              COLUMN_FAMILY_NAME,
              ByteString.copyFrom("connected_cell".getBytes()),
              timestamp,
              zero);

  dataClient.mutateRow(rowMutation);
} catch (Exception e) {
  System.out.println("Error during update2: \n" + e.toString());
}

6. 选择

现在,您将检索已写入表中的数据。迁移 CQL select 语句时,您需要考虑 select 语句的多个方面,例如列、通过 where 子句进行过滤,以及限制和聚合函数(例如分组依据)。在这里,您只需看两个简单的 select 语句即可了解基本概念,也可以参阅相关文档,详细了解如何选择。在 Cloud Bigtable 中,有两种类型的检索操作:Get(获取)和 Scan(扫描)。Get 检索一行,而扫描则检索一定范围的行。

Cassandra

单个

cqlsh:mykeyspace> SELECT * FROM mobileTimeSeries WHERE devicetype='phone' AND deviceid = '4c410523' AND date = '2019-09-04';

多个

cqlsh:mykeyspace> SELECT * FROM mobileTimeSeries WHERE devicetype='tablet' AND deviceid = 'a0b81f74' AND date >= '2019-09-04';

Cloud Bigtable

单身

使用行查询可获取指定日期特定手机的数据,这些数据全都在一行内。这将返回值的每个带有时间戳的版本,因此您应该会在不同的时间戳看到 connected_cell 有两行。

try {
  String rowKey = "phone#4c410523#20190501";

  Row row = dataClient.readRow(tableId, rowKey);
  for (RowCell cell : row.getCells()) {

    System.out.printf(
        "Family: %s    Qualifier: %s    Value: %s    Timestamp: %s%n",
        cell.getFamily(),
        cell.getQualifier().toStringUtf8(),
        cell.getValue().toStringUtf8(),
        cell.getTimestamp());
  }
} catch (Exception e) {
  System.out.println("Error during lookup: \n" + e.toString());
}

多个

使用范围扫描可查看指定移动设备一个月内的数据(这些数据分布在多个行中)。您可以对过滤条件使用过滤条件,以便仅获取特定版本的数据或过滤值。

try {
  Query query = Query.create(tableId).range("tablet#a0b81f74#201905", "tablet#a0b81f74#201906");
  ServerStream<Row> rowStream = dataClient.readRows(query);
  for (Row row : rowStream) {
    System.out.println("Row Key: " + row.getKey().toStringUtf8());
    for (RowCell cell : row.getCells()) {

      System.out.printf(
          "Family: %s    Qualifier: %s    Value: %s    Timestamp: %s%n",
          cell.getFamily(),
          cell.getQualifier().toStringUtf8(),
          cell.getValue().toStringUtf8(),
          cell.getTimestamp());
    }
  }
} catch (Exception e) {
  System.out.println("Error during scan: \n" + e.toString());
}

7. 删除

在这里,您可以删除已放入表格中的数据。首先,您需要删除一行,然后需要删除多行。

Cassandra 的 CQL 支持在指定了所有主要列的情况下删除单行和范围。您可以对 Bigtable 执行此操作,方法是扫描某个范围,然后执行行级删除。请注意,您得到的结果是相同的,但会有更多操作,因为每次删除都是自己的操作。

Cassandra

单个

cqlsh:mykeyspace> DELETE from mobileTimeSeries where devicetype='phone' and deviceid = '4c410523';

多个

cqlsh:mykeyspace> DELETE from mobileTimeSeries where devicetype='tablet' and deviceid = 'a0b81f74';

Cloud Bigtable

单个

在这里,您可以删除特定电话号码和日期的数据。使用行键一次删除一行。

try {
  String rowKey = "phone#4c410523#20190501";

  RowMutation mutation = RowMutation.create(tableId, rowKey).deleteRow();

  dataClient.mutateRow(mutation);
} catch (Exception e) {
  System.out.println("Error during Delete: \n" + e.toString());
}

多个

在这里,您可以删除特定移动设备的所有数据。要迁移删除多行的 CQL 查询,您需要执行扫描,然后使用生成的一组行键删除每一行。

try {
  Query query = Query.create(tableId).prefix("tablet#a0b81f7");
  ServerStream<Row> rowStream = dataClient.readRows(query);
  BulkMutation bulkMutation = BulkMutation.create(tableId);
  for (Row row : rowStream) {
    bulkMutation.add(row.getKey(), Mutation.create().deleteRow());
  }

  dataClient.bulkMutateRows(bulkMutation);
} catch (Exception e) {
  System.out.println("Error during DeleteMultiple: \n" + e.toString());
}

8. 即将完成

清理

Cassandra

如果您创建了 Cassandra 集群来执行此操作,请照常将其删除。

Cloud Bigtable

如果您在现有 Cloud Bigtable 实例上创建了表,则可以使用 cbt 命令将其删除

cbt deletetable mobile-time-series

如果您使用了模拟器,只需在启动模拟器的终端中输入 CTRL-C,停止模拟器以清除所有工作。

后续步骤