1. 简介
在此 Codelab 中,您将了解如何将 Cloud Bigtable 与 Java HBase 客户端搭配使用。
您将了解如何
- 避免架构设计方面的常见错误
- 导入序列文件中的数据
- 查询数据
完成后,您将获得多张显示纽约市公交车数据的地图。例如,您将创建以下曼哈顿公交行程热图:

您如何评价自己在使用 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”按钮打开它。

设置以下环境变量,以便更轻松地复制和粘贴 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 包含每个步骤的可视化效果,如果您只想跟着操作,可以参考这些可视化效果。以下是第一个查询的结果:

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

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

对该查询进行一项有趣的修改,即可查看 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 中,则可以查看公交路线的热图。橙色斑点表示经停点,鲜红色斑点表示路线的起点和终点。

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
向东行驶的公交车

向西行驶的公交车

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

11. 完成
清理相关资源以避免产生费用
为避免因本 Codelab 中使用的资源导致您的 Google Cloud Platform 账号产生费用,您应删除您的实例。
gcloud bigtable instances delete $INSTANCE_ID
所学内容
- 架构设计
- 设置实例、表和列族
- 使用 Dataflow 导入序列文件
- 使用查找、扫描、带过滤条件的扫描和多范围扫描进行查询
后续步骤
- 如需详细了解 Cloud Bigtable,请参阅文档。
- 试用其他 Google Cloud Platform 功能。查阅我们的教程。
- 了解如何通过 OpenTSDB 集成监控时间序列数据