Cloud Bigtable 简介

1. 简介

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

您将了解如何

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

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

7349d94f7d41f1d1

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

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

您打算如何使用本教程?

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

2. 关于数据集

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

3. 架构设计

为了使 Cloud Bigtable 达到最佳性能,您在设计架构时必须深思熟虑。Cloud Bigtable 中的数据按字典顺序自动排序,因此如果您精心设计架构,查询相关数据会非常高效。Cloud Bigtable 允许通过按行键进行点查询或返回一组连续行的行范围扫描来进行查询。但是,如果架构没有经过深思熟虑,您可能会发现自己将多次行查找拼凑在一起,更糟糕的是执行全表扫描(操作速度极其缓慢)。

规划查询

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

根据这些信息,您可以执行以下查询:

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

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

在此 Codelab 中,您将着重优化和执行下面一组查询:

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

设计行键

在此 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 日零点到凌晨 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. 执行扫描

现在,我们来看看公交线路在该小时内的所有数据。扫描代码与 get 代码非常相似。您为扫描仪提供一个起始位置,然后指明您只想要时间戳为 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

“Google 地图制作工具”应用可一次显示多个列表,方便您查看第一次查询时,哪些公交车是车辆。

234c1b51e3b201e

对此查询的一个有意思的修改是查看 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

将结果复制到地图制作工具后,您就可以查看公交路线的热图。橙色 blob 表示停靠站,亮红色 blob 表示路线的起点和终点。

346f52e61b3d8902

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

向西行驶的公交车

2b5771ee9046399f

通过比较这两个热图,您可以看到路线的差异以及速度上的差异。数据解读之一是,在向西行驶的公路上,公交车会更多地停靠,尤其是在进入中央公园时。在往东的公交车上,你不会发现很多阻塞点。

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

11. 完成

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

为避免系统因此 Codelab 中使用的资源向您的 Google Cloud Platform 账号收取费用,您应删除您的实例。

gcloud bigtable instances delete $INSTANCE_ID

所学内容

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

后续步骤