用Kafka和HBase构建一个基于Docker的数据采集器


【编者的话】本文主要介绍在Docker上,用Kafka和HBase构建一个数据采集器,并用这个采集器用来记录Caltrain Rider这款应用的GPS数据。本文只是一个简单的实践,读者可以将此方法进行拓展,以更好的学习Docker。

不难看出Docker近来发展迅速。分布式计算现在已日益普遍,而适用于分布式环境的开发工具仍在发展之中。一个多平台的应用在开发、测试以及部署方面已经成为一大难题,但好在虚拟机为我们提供了一个非常有用的简化抽象概念,允许应用的依赖性独立于物理硬件进行配置,但容器化技术通过运行宿主机的独立进程,避免了与虚拟化硬件竞争,从而进一步实现了这一点。同时,Docker是管理Linux容器的一个重要工具,也具备了分享容器镜像和协同工作的功能。

本文中将使用一个例子进行详述,即使用Docker构建数据采集途径,从而运用Kafka和HBase记录手机应用程序GPS数据,它是基于Caltrain Rider应用的部分后端基础架构,以帮助使用者查询列车时刻,但是本文中我提及的方法可广泛适用于诸多方面。文中将列举几个可以无缝工作的Docker容器:
  • 一个收集数据的Java REST服务器
  • 一个管理多用户GPS数据信息的Kafka应用
  • 一个存储日志的HBase服务器
  • 一个管理Kafka和HBase的Zookeeper应用


使用Docker Compose(之前叫Fig),我们可以更为简单易行地配置和管理这些容器,同时也可以在GitHub上找到本文的所有代码。

Sittin' on the Docker Container

按照步骤,首先,确保你已经安装Docker和Compose,然后就开始克隆GitHub仓库 吧,GitHub上有详细的安装步骤以及所有构建项目所需要的代码,同时也有最新版本的Dockerfile和docker-compose.yml,本文将从头开始介绍这些内容。这一示例使用的是OSX上的boot2docker ,如果你在Linux或Windows上,那可能需要进行一些改动。

我们先以一个单独容器为例:Java REST服务器。正如上文所说,这个服务器为手机应用程序Caltrain Rider的GPS数据提供了一个终端,同时也成为了下文中Kafka应用的生产者。在本例中,我们已经拥有了一个依托于Maven所构建的可操作的Java服务器,并希望其在Docker容器中运行。我们只需要在工作目录中创建一个Dockerfile,并在其中添加如下一行指令:
FROM maven:3.2.5-jdk-8u40-onbuild

这条指令告诉Docker使用Maven官方镜像仓库中的指定镜像,它可方便替换原有的Maven或JDK版本,(类似的镜像对于语言选择也同样有效)-onbuild后缀适用于一个镜像,该镜像会自动为容器添加项目目录,并安装运行mvn(run mvn install)。现在我们可以通过geolocationservice目录,使用Docker构建并运行容器:
docker build -t svdsdemo/geolocationservice .
docker run -p 8080:8080 svdsdemo/geolocationservice java -jar target/geolocationservice-1.0-SNAPSHOT.jar

第一条指令告诉Docker在当前目录中构建Dockerfile文件,并将结果镜像标记为svdsdemo/geolocationservice。第二条指令运行了第一条指令中生成的镜像,并通过-p将容器中的8080端口映射到了Docker宿主机的8080端口,然后通过 java -jar target/geolocationservice-1.0-SNAPSHOT.jar 这条指令运行Java应用。如果你使用的是boot2docker,那么Docker Machine便不是宿主机,而是虚拟机,你可以通过执行 boot2docker ip 指令查看虚拟机的IP地址。现在我们可以通过要求测试值作为返回的JSON数据,从而确认rest服务器是否运行:
[bash]$ curl http:// $(boot2docker ip):8080/geoLocation/v1/testValue?a=b

{
"a": [
"b"
]
}
容器正常运行!但是该服务的真实目标是将GPS的日志记录到HBase上。现在,我们可以尝试PUT一些GPS数据:
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/200/1.0

此时容器报错,提示我们不能链接到Kafka,这一问题我们将在下一节中进行说明。但是针对当前容器,首先来看几个调整的地方。

在构建过程中,如果你对Java代码做了一些修改,Maven就会从Scratch下载所有的依赖。由于我们的依赖包不需要频繁修改,这样做并没有意义,因此我们可以利用Docker自带的缓存机制快速构建所需容器。首先修改Dockerfile文件将-onbuild后缀去掉,在构建时先从POM文件开始(我们很少修改它),然后再构建剩下的代码(这些代码需要更为频繁地修改)。因此在我们构建代码时,POM文件里面的依赖已在镜像之中,修改如下:
FROM maven:3.2.5-jdk-8u40

RUN mkdir --parents /usr/src/app
WORKDIR /usr/src/app

# selectively add the POM file
ADD pom.xml /usr/src/app/

# get all the downloads out of the way & cached
RUN mvn verify clean --fail-never

ADD . /usr/src/app
RUN mvn verify

现在,为了方便在多容器中进行部署,我们开始引入Docker Compose,以使在多个容器中添加时更为容易。在项目的根目录中,我们将创建一个docker-compose.yml配置文件:
service:
build: ./geolocationservice/
command: java -jar target/geolocationservice-1.0-SNAPSHOT.jar
ports:
- "8080:8080"

这个配置文件主要是Docker的一些配置参数,允许我们以下列命令启动容器:
[bash]$ docker-compose build
[bash]$ docker-compose up

到目前为止,操作都很简单。而在下一节中,我们将要连接多个容器,操作难度将大幅提升!

Kafka Trial

现在介绍Kafka。在公共Docker镜像的基础上,我们只需要在docker-compose.yml配置文件中添加下面几行代码:
zookeeper:
image: oddpoet/zookeeper
hostname: zookeeper
command:
- "2181"
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka
hostname: kafka
ports:
- "9092:9092"
links: 
- zookeeper:zk
environment:
KAFKA_ADVERTISED_PORT: 9092
service:
build: ./geolocationservice/
command: java -jar target/geolocationservice-1.0-SNAPSHOT.jar
ports:
- "8080:8080"
links:
- kafka

我们几乎无需进行更多的操作,便可以拥有3个容器,并且能够彼此连接。这主要归功于link 属性,它将hostname代表的主机映射到了目标容器。比如,在服务端容器里,Kafka主机将会链接到Kafka容器, Zookeeper和Kafka镜像在设置端口属性时会有细微的区别,Zookeeper镜像将端口作为命令行参数,而Kafka镜像则将端口设置为一个环境变量。我们现在测试一下应用程序编程接口:
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/200/1.0

{
"id": "FAKEID",
"latitude": 40.11,
"longitude": 88.27,
"epoch": 200,
"accuracy": 1.0
}
哈,我们得到了预想的结果!

接下来,我们添加一个简单用户,测试一下Kafka是否真的接收到了GPS数据。同服务端一样,它已经建立了一个Java应用,而且我们可以重复使用同一个Dockerfile。但是还要在docker-compose.yml配置文件中添加如下的几行代码:
basicconsumer:
build: ./genericconsumer/
command: java -jar target/genericconsumer-1.0-SNAPSHOT-jar-with-dependencies.jar --zookeeper zookeeper:2181 --groupid 11 --topicname GeoLocation --threads 2 --consumerclass com.svds.genericconsumer.consumers.BasicConsumer
links:
- kafka
- zookeeper

注意:配置这项服务时我们使用了几个其它参数,这与本文内容无关。消费者Kafka配置完成,它能将接收到的消息进行标准输出。我们可以执行下面的几行代码来查看这一用户的情况:
[bash]$ docker-compose build
[bash]$ docker-compose up -d
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/200/1.0
[bash]$ docker-compose logs basicconsumer
[bash]$ docker-compose stop

这里,docker-compose up -d 命令在后台运行容器,docker-compose logs basicconsumer 命令会为我们呈现basicconsumer的日志信息。如果我们刚刚创建的用户接收到了GPS信息,我们将会看到类似的日志信息:
basicconsumer_1 | CONSUMER: FAKEID/40.11/88.27/200/1.0

现在我们已有4个容器同时运行,在下一节中我们将添加HBase应用的相关内容。

Turn up the HBase

在使用HBase之前,我们要在docker-compose.yml配置文件中加入几行代码:
hbase:
image: kevinsvds/hbase
hostname: hbase
ports:
 - "9090:9090"
 - "9095:9095"
 - "60000:60000"
 - "60010:60010"
 - "60020:60020"
 - "60030:60030"
links:
 - zookeeper
geolocationconsumer:
build: ./genericconsumer/
command: java -jar target/genericconsumer-1.0-SNAPSHOT-jar-with-dependencies.jar --zookeeper zookeeper:2181 --groupid 22 --topicname GeoLocation --threads 2 --consumerclass com.svds.genericconsumer.consumers.GeoLocationConsumer --parameters zk=zookeeper,hbaseTable=gps-test
links:
- kafka
- zookeeper
- hbase

HBase使用的镜像是oddpoet/hbase-local,它包含了thrift API。有了thrift API,我们便可用Python查看HBase tables。geolocationconsumer和genericconsumer类似,不过两者在命令行参数上会有所不同,geolocationconsumer在命令行参数上配置了HBase的连接。

另外,我尚未提及主机的配置,而这对HBase服务来说非常重要!因为 Java API通过Zookeeper得知HBase的主机名,这样它就可以得到HBase的连接信息,因此HBase的主机名必须能够被Java服务解析。在默认状态下,HBase的主机名设置成为容器的ID,而 geolocationconsumer 却无法识别。但是,使用容器的link属性,geolocationconsumer 就可以识别HBase的主机名。
我们构建一个新的镜像,并在其中插入一些数据:
[bash]$ docker-compose build
[bash]$ docker-compose up -d
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/1420183320000/1.0
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/1420183321000/1.0
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/1420183322000/1.0
[bash]$ curl -X PUT http:// $(boot2docker ip):8080/geoLocation/v1/setGeoLocation/FAKEID/40.11/88.27/1420183323000/1.0

测试上面的指令是否成功执行有许多方法,但在这里我们使用Python连接HBase进行测试,如果需要的话,可以用pip安装happybase,然后写一个短的Python脚本(你也可以在 iPython 中执行这些脚本):
import happybase
# connect to HBase on the boot2docker ip
connection = happybase.Connection('192.168.59.103',9090) 
table = connection.table('gps-test')
for k,data in table.scan():
print k, data

如果一切顺利,我们可以在HBase table中看到如下的信息:
2015-1-2-1420183320000-FAKEID {'locations:id': 'FAKEID', 'locations:latitude': '40.11', 'locations:epoch': '1420183320000', 'locations:longitude': '88.27', 'locations:accuracy': '1.0'}
2015-1-2-1420183321000-FAKEID {'locations:id': 'FAKEID', 'locations:latitude': '40.11', 'locations:epoch': '1420183321000', 'locations:longitude': '88.27', 'locations:accuracy': '1.0'}
2015-1-2-1420183322000-FAKEID {'locations:id': 'FAKEID', 'locations:latitude': '40.11', 'locations:epoch': '1420183322000', 'locations:longitude': '88.27', 'locations:accuracy': '1.0'}
2015-1-2-1420183323000-FAKEID {'locations:id': 'FAKEID', 'locations:latitude': '40.11', 'locations:epoch': '1420183323000', 'locations:longitude': '88.27', 'locations:accuracy': '1.0'
}
这里,我们可以通过执行 docker-compose stop 关闭所有的服务。但是,如果你只想对上文创建的简单用户做出修改,而不想关闭和重启其它服务,你可以执行 docker-compose stop basicconsumer 这个命令,然后再做出修改。当修改完成后,你可以执行 docker-compose build basicconsumer 进行重新构建,接着执行 docker-compose up -d --no-deps basicconsumer 运行(--no-deps 标志会指令docker-compose不要重新创建依赖,因为它们已处于运行状态)。

同样,在默认状态下,执行 docker-compose up 命令时会重新创建容器,并进行初始化设置,尤其是原先保存在HBase中的数据都会被删除。如果不想这样,你可以在后台执行如下命令:docker-compose up --no-recreate 或者 docker-compose up --no-recreate -d

这仅仅是一个简单的测试,除此以外还可以进行更多的操作!比如你可以对一些Kafka用户的GPS日志信息或者不同的日志数据上进行额外的处理。另外,你也可以在一个单一实例的伪分布式安装过程中或在一个完全分布式的集群里量化HBase(或Kafka),甚至,你也可以添加Impala或Hive来查询日志数据。

你这里在这个参考手册中查看本例中涉及到的一些指令。

原文链接:Using Docker to build a data acquisition pipeline with Kafka and HBase(翻译:王辉 校对:李颖杰)

===============================================
译者介绍:
王辉,大二学生,计算机专业,邮箱:wanghui94@live.com

0 个评论

要回复文章请先登录注册