Cloud Bigtable简介

在此代码实验室中,将向您介绍如何将Cloud BigtableJava HBase client一起使用

您将学习如何

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

完成后,您将有几张显示纽约市公交车数据的地图。例如,您将创建此曼哈顿公交旅行的热图:

7349d94f7d41f1d1.png

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

新手中间的精通

您将如何使用本教程?

仅阅读阅读并完成练习

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

为了从Cloud Bigtable获得最佳性能,在设计架构时必须考虑周全。 Cloud Bigtable中的数据是字典顺序自动排序的,因此,如果您精心设计架构,则查询相关数据将非常有效。 Cloud Bigtable允许通过按行键或行范围扫描的点查找使用查询,以返回连续的行集。但是,如果您的架构没有经过深思熟虑,则可能会发现自己将多个行查找拼凑在一起,或者更糟糕的是进行全表扫描,这是非常慢的操作。

计划查询

我们的数据包含各种信息,但是对于此代码实验室,您将使用总线的位置目的地

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

  • 获取给定小时内一辆公共汽车的位置。
  • 获取公交线路或特定公交车一天的数据。
  • 在地图上的矩形中找到所有公交车。
  • 获取所有总线的当前位置(如果您实时摄取此数据)。

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

对于此代码实验室,您将专注于优化和执行以下查询集:

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

设计行键

对于此代码实验室,您将使用静态数据集,但将设计可扩展性的架构。您将设计一个架构,该架构允许您将更多总线数据流式传输到表中,并且仍然可以正常运行。

这是行键的建议架构:

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

对于此代码实验室,您将使用一个列族来简化事情。这是数据外观的示例视图。数据按行键排序。

行键

cf:VehicleLocation.Latitude

cf:VehicleLocation.Longitude

...

MTA / M86-SBS / 1496275200000 / NYCT_5824

40.781212 @ 20:52:54.00 40.776163 @ 20:43:19.00 40.778714 @ 20:33:46.0​​0

-73.961942 @ 20:52:54.00 -73.946949 @ 20:43:19.00 -73.953731 @ 20:33:46.0​​0

...

MTA / M86-SBS / 1496275200000 / NYCT_5840

40.780664 @ 20:13:51.00 40.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.00 40.779961 @ 20:43:15.00 40.788416 @ 20:33:44.00

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

...

...

...

...

...

接下来,您将创建一个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附带了您将在此代码实验室中使用的工具,已经安装的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

使用以下步骤从gs://cloud-bigtable-public-datasets/bus-data导入此代码实验室的一组序列文件

通过运行此命令来启用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 UI中监视作业。此外,您还可以使用其监视UI来查看Cloud Bigtable实例上的负载。整个导入过程需要5分钟。

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/

您将执行的第一个查询是一个简单的行查找。您将在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 App中以可视化结果。几层之后,它将告诉您创建一个免费帐户。您可以创建一个帐户,也可以删除现有的图层。如果您只是想继续学习,此代码实验室将为每个步骤提供可视化效果。这是第一个查询的结果:

f1a1fac6051c6210.png

现在,让我们查看该小时内公交线路的所有数据。扫描代码看起来与获取代码非常相似。您给扫描仪一个开始位置,然后指示您只希望在时间戳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

接下来,您将过滤向东行驶的巴士和向西行驶的巴士,并为每个巴士创建一个单独的热图。

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

通过比较两个热图,您可以看到路线上的差异以及步调上的差异。数据的一种解释是,在向西的路线上,公交车的停驶次数更多,尤其是在进入中央公园时。在往东行驶的公交车上,您实际上看不到很多阻塞点。

对于最后的查询,当您关心某个区域中的许多总线时,将解决此情况:

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

清理以避免收费

为避免对该代码实验室中使用的资源造成Google Cloud Platform帐户费用,您应该删除实例。

gcloud bigtable instances delete $INSTANCE_ID

我们涵盖的内容

  • 模式设计
  • 设置实例,表和家族
  • 使用数据流导入序列文件
  • 使用查询,扫描,带过滤器的扫描和多范围扫描进行查询

下一步