Cloud Bigtable 简介

1. 简介

在此 Codelab 中,您将了解如何将 Cloud BigtableJava HBase 客户端搭配使用。

您将了解如何

  • 避免架构设计方面的常见错误
  • 导入序列文件中的数据
  • 查询数据

完成后,您将获得多张显示纽约市公交车数据的地图。例如,您将创建以下曼哈顿公交行程热图:

7349d94f7d41f1d1.png

您如何评价自己在使用 Google Cloud Platform 方面的经验水平?

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

您打算如何使用本教程?

仅通读 阅读并完成练习

2. 关于数据集

您将看到一个关于纽约市公交车的数据集。有 300 多条公交路线,5,800 辆车在这些路线上行驶。我们的数据集是一个日志,其中包含目的地名称、车辆 ID、纬度、经度、预计到达时间和预定到达时间。该数据集由 2017 年 6 月拍摄的快照组成,拍摄间隔约为 10 分钟。

3. 架构设计

为了使 Cloud Bigtable 达到最佳性能,您必须在设计架构时考虑周全。Cloud Bigtable 中的数据会按字典顺序自动排序,因此如果您设计好架构,查询相关数据会非常高效。Cloud Bigtable 允许使用行键进行点查找,或进行行范围扫描以返回一组连续的行。不过,如果您的架构考虑不周,您可能会发现自己需要拼凑多次行查找,或者更糟糕的是,需要执行全表扫描,而这是一种非常缓慢的操作。

规划查询

我们的数据包含各种信息,但在此 Codelab 中,您将使用公交车的位置目的地

有了这些信息,您就可以执行以下查询:

  • 获取指定小时内单辆公交车的位置。
  • 获取公交线路或特定公交车一天的相关数据。
  • 查找地图上某个矩形区域内的所有公交车。
  • 获取所有公交车的当前位置(如果您实时提取此数据)。

这组查询无法同时以最佳方式完成。例如,如果您按时间排序,则无法在不执行全表扫描的情况下基于位置进行扫描。您需要根据最常运行的查询来确定优先级。

在此 Codelab 中,您将重点关注如何优化和执行以下查询:

  • 获取特定车辆在一小时内的位置。
  • 获取整个公交线路在一小时内的位置信息。
  • 在一小时内获取曼哈顿所有公交车的位置。
  • 获取曼哈顿所有公交车在 1 小时内的最新位置。
  • 获取整条公交线路在一个月内的位置信息。
  • 获取在 1 小时内到达特定目的地的整条公交线路的位置。

设计行键

在此 Codelab 中,您将使用静态数据集,但会设计一个可扩缩的架构。您将设计一个架构,以便将更多公交数据流式传输到表中,同时仍能保持良好的性能。

以下是建议的行键架构:

[公交公司/公交线路/时间戳(向下舍入到小时)/车辆 ID]。每行包含一小时的数据,每个单元格包含多个带时间戳的数据版本。

在此 Codelab 中,您将使用一个列族,以简化操作。以下是数据外观的示例视图。数据按行键排序。

行键

cf:VehicleLocation.Latitude

cf:VehicleLocation.Longitude

MTA/M86-SBS/1496275200000/NYCT_5824

40.781212 @20:52:54.0040.776163 @20:43:19.0040.778714 @20:33:46.00

-73.961942 @20:52:54.00-73.946949 @20:43:19.00-73.953731 @20:33:46.00

MTA/M86-SBS/1496275200000/NYCT_5840

40.780664 @20:13:51.0040.788416 @20:03:40.00

-73.958357 @20:13:51.00 -73.976748 @20:03:40.00

MTA/M86-SBS/1496275200000/NYCT_5867

40.780281 @20:51:45.0040.779961 @20:43:15.0040.788416 @20:33:44.00

-73.946890 @20:51:45.00-73.959465 @20:43:15.00-73.976748 @20:33:44.00

4. 创建实例、表和列族

接下来,您将创建一个 Cloud Bigtable 表。

首先,创建一个新项目。使用内置的 Cloud Shell,您可以通过点击右上角的“激活 Cloud Shell”按钮打开它。

a74d156ca7862b28.png

设置以下环境变量,以便更轻松地复制和粘贴 Codelab 命令:

INSTANCE_ID="bus-instance"
CLUSTER_ID="bus-cluster"
TABLE_ID="bus-data"
CLUSTER_NUM_NODES=3
CLUSTER_ZONE="us-central1-c"

Cloud Shell 预安装了本 Codelab 中将要使用的工具,包括 gcloud 命令行工具cbt 命令行界面Maven

运行以下命令,启用 Cloud Bigtable API。

gcloud services enable bigtable.googleapis.com bigtableadmin.googleapis.com

运行以下命令来创建实例:

gcloud bigtable instances create $INSTANCE_ID \
    --cluster=$CLUSTER_ID \
    --cluster-zone=$CLUSTER_ZONE \
    --cluster-num-nodes=$CLUSTER_NUM_NODES \
    --display-name=$INSTANCE_ID

创建实例后,填充 cbt 配置文件,然后运行以下命令来创建表和列族:

echo project = $GOOGLE_CLOUD_PROJECT > ~/.cbtrc
echo instance = $INSTANCE_ID >> ~/.cbtrc

cbt createtable $TABLE_ID
cbt createfamily $TABLE_ID cf

5. 导入数据

按照以下步骤,从 gs://cloud-bigtable-public-datasets/bus-data 为此 Codelab 导入一组序列文件

运行以下命令,启用 Cloud Dataflow API。

gcloud services enable dataflow.googleapis.com

运行以下命令以导入表格。

NUM_WORKERS=$(expr 3 \* $CLUSTER_NUM_NODES)
gcloud beta dataflow jobs run import-bus-data-$(date +%s) \
--gcs-location gs://dataflow-templates/latest/GCS_SequenceFile_to_Cloud_Bigtable \
--num-workers=$NUM_WORKERS --max-workers=$NUM_WORKERS \
--parameters bigtableProject=$GOOGLE_CLOUD_PROJECT,bigtableInstanceId=$INSTANCE_ID,bigtableTableId=$TABLE_ID,sourcePattern=gs://cloud-bigtable-public-datasets/bus-data/*

监控导入过程

您可以在 Cloud Dataflow 界面中监控作业。此外,您还可以通过 Cloud Bigtable 实例的监控界面查看其负载。整个导入过程应该需要 5 分钟。

6. 获取代码

git clone https://github.com/googlecodelabs/cbt-intro-java.git
cd cbt-intro-java

运行以下命令,切换到 Java 11:

sudo update-java-alternatives -s java-1.11.0-openjdk-amd64 && export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/

7. 执行查找

您将执行的第一个查询是简单的行查找。您将获得 2017 年 6 月 1 日凌晨 12:00 至凌晨 1:00 期间 M86-SBS 线路上的公交车数据。当时,ID 为 NYCT_5824 的车辆在该公交线路上。

有了这些信息,并了解架构设计(公交公司/公交线路/时间戳(向下舍入到小时)/车辆 ID),您可以推断出行键为:

MTA/M86-SBS/1496275200000/NYCT_5824

BusQueries.java

private static final byte[] COLUMN_FAMILY_NAME = Bytes.toBytes("cf");
private static final byte[] LAT_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Latitude");
private static final byte[] LONG_COLUMN_NAME = Bytes.toBytes("VehicleLocation.Longitude");

String rowKey = "MTA/M86-SBS/1496275200000/NYCT_5824";
Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

结果应包含该小时内公交车的最新位置。但您想查看所有位置,因此请在 GET 请求中设置版本数量上限。

BusQueries.java

Result getResult =
    table.get(
        new Get(Bytes.toBytes(rowKey))
            .setMaxVersions(Integer.MAX_VALUE)
            .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
            .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME));

在 Cloud Shell 中,运行以下命令以获取该公交车在一小时内的纬度和经度列表:

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=lookupVehicleInGivenHour

您可以将纬度和经度复制并粘贴到 MapMaker 应用中,以直观呈现结果。在几个图层之后,系统会提示您创建免费账号。您可以创建账号,也可以只删除现有的图层。此 Codelab 包含每个步骤的可视化效果,如果您只想跟着操作,可以参考这些可视化效果。以下是第一个查询的结果:

f1a1fac6051c6210.png

8. 执行扫描

现在,我们来查看该小时内公交线路的所有数据。扫描代码与获取代码非常相似。您为扫描器提供了一个起始位置,然后指示您只需要时间戳 1496275200000 所表示的小时内的 M86-SBS 公交线路的行。

BusQueries.java

Scan scan;
scan = new Scan();
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME)
    .withStartRow(Bytes.toBytes("MTA/M86-SBS/1496275200000"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/1496275200000"));
ResultScanner scanner = table.getScanner(scan);

运行以下命令以获取结果。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanBusLineInGivenHour

c18a4ac6522d08a2.png

Map Maker 应用可以同时显示多个列表,因此您可以查看哪些公交车是您运行的第一个查询中的车辆。

234c1b51e3b201e.png

对该查询进行一项有趣的修改,即可查看 M86-SBS 公交线路的整个月的数据,这非常容易实现。只需从起始行和前缀过滤器中移除时间戳即可获得结果。

BusQueries.java

scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));

// Optionally, reduce the results to receive one version per column
// since there are so many data points.
scan.setMaxVersions(1);

运行以下命令以获取结果。(系统会显示长长的结果列表。)

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanEntireBusLine

如果您将结果复制到 MapMaker 中,则可以查看公交路线的热图。橙色斑点表示经停点,鲜红色斑点表示路线的起点和终点。

346f52e61b3d8902.png

9. 介绍过滤条件

接下来,您将按向东行驶的公交车和向西行驶的公交车进行过滤,并为每种公交车分别创建一个热图。

BusQueries.java

Scan scan;
ResultScanner scanner;
scan = new Scan();
SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Yorkville East End AV"));

scan.setMaxVersions(1)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M86-SBS/"))
    .setRowPrefixFilter(Bytes.toBytes("MTA/M86-SBS/"));
scan.setFilter(valueFilter);
scanner = table.getScanner(scan);

运行以下命令,获取向东行驶的公交车的结果。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingEast

如需获取向西行驶的公交车,请更改 valueFilter 中的字符串:

BusQueries.java

SingleColumnValueFilter valueFilter =
    new SingleColumnValueFilter(
        COLUMN_FAMILY_NAME,
        Bytes.toBytes("DestinationName"),
        CompareOp.EQUAL,
        Bytes.toBytes("Select Bus Service Westside West End AV"));

运行以下命令,获取向西行驶的公交车的结果。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=filterBusesGoingWest

向东行驶的公交车

76f6f62096a6847a.png

向西行驶的公交车

2b5771ee9046399f.png

通过比较这两个热图,您可以了解路线的差异,并注意到配速的差异。对这些数据的一种解读是,在向西行驶的路线中,公交车被拦停的次数更多,尤其是在进入中央公园时。而在向东行驶的公交车上,您不会看到很多瓶颈。

10. 执行多范围扫描

对于最终查询,您将解决需要关注某个区域内多条公交线路的情况:

BusQueries.java

private static final String[] MANHATTAN_BUS_LINES = {"M1","M2","M3",...

Scan scan;
ResultScanner scanner;
List<RowRange> ranges = new ArrayList<>();
for (String busLine : MANHATTAN_BUS_LINES) {
  ranges.add(
      new RowRange(
          Bytes.toBytes("MTA/" + busLine + "/1496275200000"), true,
          Bytes.toBytes("MTA/" + busLine + "/1496275200001"), false));
}
Filter filter = new MultiRowRangeFilter(ranges);
scan = new Scan();
scan.setFilter(filter);
scan.setMaxVersions(Integer.MAX_VALUE)
    .addColumn(COLUMN_FAMILY_NAME, LAT_COLUMN_NAME)
    .addColumn(COLUMN_FAMILY_NAME, LONG_COLUMN_NAME);
scan.withStartRow(Bytes.toBytes("MTA/M")).setRowPrefixFilter(Bytes.toBytes("MTA/M"));
scanner = table.getScanner(scan);

运行以下命令以获取结果。

mvn package exec:java -Dbigtable.projectID=$GOOGLE_CLOUD_PROJECT \
-Dbigtable.instanceID=$INSTANCE_ID -Dbigtable.table=$TABLE_ID \
-Dquery=scanManhattanBusesInGivenHour

7349d94f7d41f1d1.png

11. 完成

清理相关资源以避免产生费用

为避免因本 Codelab 中使用的资源导致您的 Google Cloud Platform 账号产生费用,您应删除您的实例。

gcloud bigtable instances delete $INSTANCE_ID

所学内容

  • 架构设计
  • 设置实例、表和列族
  • 使用 Dataflow 导入序列文件
  • 使用查找、扫描、带过滤条件的扫描和多范围扫描进行查询

后续步骤