Category: 大咖专栏


使用Oracle Data Pump将数据库迁移到AWS的RDS Oracle数据库

1.Oracle数据库的迁移方法

如何将Oracle数据库从数据中心迁移到AWS云上是DBA经常遇到的问题,迁移Oracle数据库有多种方式:

(1)使用AWS DMS服务迁移

(2)使用Oracle SQL Developer迁移

(3)使用Oracle Data Pump迁移

(4)使用Oracle Export/Import迁移

(5)使用Oracle SQL Loader迁移

如果需要了解不同的迁移方法,可以参考 博客《Oracle数据库迁移到AWS云的方案》 。

2.使用Oracle Data Pump迁移

本文主要讨论使用Oracle Data Pump将Oracle数据库迁移到RDS数据库。示例数据库的信息如图。

 

下面是模拟在数据中心的Oracle11g源数据库中创建用户和表,并将源数据库迁移到AWS云中RDS Oracle 11g数据库的全过程。

步骤一:初始化源数据库并使用Data Pump导出

(1)使用SQL Plus登录源Oracle数据库

sqlplus / as sysdba

(2)创建用户test

create user test identified by welcome1;

(3)为新创建用户grant权限(实际使用请给用户grant合适的权限)

grant dba to test;

(4)为用户test创建表

create table test.aa(id varchar(20) not null primary key,name varchar2(30));

(5)为表插入数据并commit

SQL> insert into test.aa values(‘1111′,’1111name’);

1 row created.

SQL> insert into test.aa values(‘2222′,’2222name’);

1 row created.

SQL> commit;

Commit complete.

(6)在源数据库所在的Linux上逐级创建下面文件目录

mkdir /home/oracle/datapump/datafiles

(7)在SQLPlus中创建数据库Directory

create directory dpump_dir as ‘/home/oracle/datapump/datafiles’;

grant read,write on directory dpump_dir to test;

(8)使用expdp命令导出test用户的所有表

expdp test1/welcome123 directory=dpump_dir dumpfile=test.dmp

expdp test1/welcome123 directory=dpump_dir dumpfile=test1.dmp

步骤二:使用SQL Plus连接RDS数据库,并创建数据库目录对象

(1)在源数据库上配置RDS数据库的tnsnames

cd $ORACLE_HOME/network/admin

vi tnsnames.ora

输入tnsnames的内容如下:

ORARDS=(description=(address_list=(address = (protocol = TCP)(host =    RDS_HOST_NAME)(port = RDS_PORT)) )(connect_data =(SID=RDS_SID)))

(2)使用SQLPLUS连接远程RDS数据库

sqlplus oracle/welcome1@ORARDS

(3)使用tnsping检查RDS连接信息

如果连接有错误,可以使用下面命令查看通讯是否正常

tnsping “(description=(address_list=(address = (protocol = TCP)(host = RDS_HOST_NAME)(port = RDS_PORT)))(connect_data =(SID= RDS_SID)))”

tnsping应该返回“OK (xx msec)”类似文字。

如果tnsping不通请检查RDS对应的security group,RDS的security group中应当允许当前服务器通过TCP协议访问RDS数据库的端口。

(4)创建目标RDS的directory对象

exec rdsadmin.rdsadmin_util.create_directory(‘dpump_dir1’);

创建成功后退出连接RDS的SQL Plus客户端。

步骤三:将源数据库Data Pump导出的文件上传到RDS数据库

(1)连接源数据库并创建database link

create database link to_rds connect to oracle identified by welcome1 using ‘(description=(address_list=(address = (protocol = TCP)(host = RDS_HOST_NAME)(port = RDS_PORT)))(connect_data =(SID=RDS_SID)))’;

(2)运行DBMS_FILE_TRANSFER包将数据传输到RDS服务器的目录

BEGIN

DBMS_FILE_TRANSFER.PUT_FILE(

source_directory_object       => ‘dpump_dir1’,

source_file_name              => ‘test.dmp’,

destination_directory_object  => ‘dpump_dir1’,

destination_file_name         => ‘test.dmp’,

destination_database          => ‘to_rds’

);

END;

步骤四:通过impdp命令将远程的RDS数据库文件导入

(1)在源数据库服务器上运行impdp命令导入数据

impdp  oracle@ORARDS dumpfile=test.dmp directory=dpump_dir1 full=y

执行完毕检查test用户和相关的表。

3.    总结

从上面的过程我们可以看到,将一个Oracle数据库迁移到RDS的过程并不复杂,如果源数据库很大,由于需要导出数据、将数据上传到RDS的Data Pump目录、导入数据,迁移的过程也会比较长。上述过程假设了我们生产数据库的业务有足够的停机时间,在迁移过程中数据不会变化。如果迁移过程中,源数据库会发生变化,那么我们就需要同步数据中心和RDS数据库间的日志了。

如果源数据库很大,我们也可以在AWS上启动一台中间服务器,并在中间服务器上安装Oracle的客户端软件,将源数据库的Data Pump导出文件分片然后scp复制、Tsunami UDP加速上传等方式将文件上传到中间服务器,然后上传到RDS的Data Pump目录,这样能加速迁移的过程。

作者介绍:

蓝勇

AWS解决方案架构师,负责基于AWS的云计算方案架构的咨询和设计,同时致力于AWS云服务在国内的应用和推广,在DR解决方案、数据仓库、RDS服务、企业应用、自动化运维等方面有着广泛的设计和实践经验。在加入AWS之前,在甲骨文中国担任资深售前工程师,负责售前方案咨询和架构设计,在数据库,中间件,大数据及企业应用方面有丰富经验。

使用AWS的数据库迁移DMS服务

前面博客《Oracle数据库迁移到AWS云的方案》介绍了AWS数据库迁移的几种基本方法,本文主要介绍如何使用AWS的DMS服务完成数据库的迁移。

1.DMS服务介绍

为了使用户更容易的将数据库迁移到云中,AWS已经在海外区域推出了AWS Database Migration Service服务,如果您的数据库在海外,DMS可以在源数据库不停机的情况下,帮您将数据迁移到AWS云中。DMS的功能非常强大,支持同构数据库的迁移(如Oracle迁移到Oracle),也支持异构数据库直接的迁移,如Oracle到Mysql等)。在数据库迁移期间,源数据库无需停机,并且能将迁移期间数据的更改持续复制到目标数据库。因此迁移完成后,您只需在短暂的停机时间内直接切换数据库,从而保证业务数据的完整性。
在中国BJS区域,还没有推出DMS服务,但是提供了Database Migration Tool(DMT)工具,您可以使用DMT工具来完成数据库迁移。

2.使用DMS完成迁移

使用DMS服务必须确保源或目标数据库有一个在AWS云中。 使用DMS服务的步骤如下:

步骤一:Create migration

登陆AWS全球区域的Console,选择DMS,点击“Create migration”,我们便来到了“welcome”界面,从该界面我们可以看到,通过DMS进行数据迁移我们至少需要一个源数据库、目标数据库和复制实例。当然,DMS 也支持多个源数据库向一个目标数据库的迁移以及单个源数据库向多个目标数据库的迁移。迁移时,数据通过一个运行在复制实例上的任务将源数据库复制到目标数据库。点击“Next”进行复制实例的创建。

步骤二:创建“Replication Instance”

您在进行数据库迁移过程中的第一个任务是创建具有足够存储空间和处理能力的复制实例,通过复制实例来执行您分配的任务并将数据从您的源数据库迁移到目标数据库。此实例所需的大小取决于您要迁移的数据和您需要执行的任务量。具体配置参数见下表1。

如果您需要为网络和加密设置值,请选择高级选项卡。具体参数见表2。

步骤三:创建数据库连接

当您在创建复制实例时,您可以指定源和目标数据库。源数据库和目标数据库可以在AWS的EC2上,也可以是AWS的关系数据库服务(RDS)的DB实例或者本地数据库。在设置源和目标数据库时,             具体参数可以参见表3。您也可以通过高级选项卡来设置连接字符串和加密密钥的值。

等图示上部分的显示变成”Replication instance created successfully”并且“Run test“按钮变成正常,然后测试,确保测试结果为”Connection tested Successfully”,由于需要从AWS服务端连接测试数据库,因此需要设置好security group,设置的security group必须确保复制实例能够访问源和目标数据库。需要的话,可以短暂的将security group 1521 的访问设置为 0.0.0.0/0,测试成功后,点击”Next”按钮。

步骤四:创建“task”

当源数据库和目标数据库建立连接后,您需要创建一个任务来指定哪些表需要迁移,使用目标架构来映射数据并且在目标数据库中创建新表。作为创建任务的一部分,您可以选择迁移类型:迁移现有数据、迁移现有数据并复制正在进行的更改,或只复制更改的数据。

如果选择”Migrate existing data and replicate data changes”选项需要打开Task Settings 中的supplemental loging开关。在Table Mapping中Schema to Migrate选择“Oracle”,点击“Create Task”。

当您创建的task状态从creating变为ready的时候,您的task便创建好了。点击该“task”并点击上方的“Start/Resume”,您数据迁移任务便开始了!

数据库迁移完成后,目标数据库在您选择的时间段内仍会与源数据库保持同步,使您能够在方便的时候切换数据库。

3.总结

从上面过程我们可以看到,只需要简单的配置,DMS就可以帮助我们完成数据库的迁移任务,并且DMS服务是免费的,迁移过程中用到的资源是收费的。

作者介绍:

蓝勇

AWS解决方案架构师,负责基于AWS的云计算方案架构的咨询和设计,同时致力于AWS云服务在国内的应用和推广,在DR解决方案、数据仓库、RDS服务、企业应用、自动化运维等方面有着广泛的设计和实践经验。在加入AWS之前,在甲骨文中国担任资深售前工程师,负责售前方案咨询和架构设计,在数据库,中间件,大数据及企业应用方面有丰富经验。

Oracle数据库迁移到AWS云的方案

当前云已经成为常态,越来越多的企业希望使用云来增加基础设施的弹性、减轻基础设施的维护压力,运维的成本等。很多企业使用云碰到的难题之一是如何将现有的应用迁移到云上,将现有应用的中间件系统、Web系统及其他组件迁移到云上相对容易,一般只需要重新部署或复制即可,但如何将数据库迁移到AWS云中,是很多企业需要面对的一个难题。由于数据库的种类繁多,本文将以Oracle数据库为例,介绍将数据中心的Oracle迁移到云中的基本知识,不同方法涉及的迁移过程,请参考后续的博客。

1.云中数据库的模式

如果要在云中使用Oracle数据库,有两种选择:

  • EC2服务器模式

使用AWS的EC2服务器,在EC2服务器上手工安装Oracle数据库软件,用户需要自己准备Oracle的License,这和用户自己在机房安装Oracle数据库类似。如果在中国以外的区域,用户也可以使用AWS Marketplace里面的不同版本的Oracle镜像,直接初始化Oracle数据库,这种情况你也需要自己准备Oracle的License。

  • RDS模式

Amazon Relational Database Service (Amazon RDS) 是一种 AWS提供的Web 服务,可以让我们更轻松地在云中设置、 操作和扩展关系数据库,减少管理关系型数据库复杂的管理任务。RDS包括了Oracel、SQL Server、My SQL,等多种数据库引擎,你可以根据需要选择数据库的类型。

根据我们使用模式的不同,能选择的迁移方式也不同。

2.逻辑迁移和物理迁移

数据库的迁移可以分为逻辑迁移和物理迁移两种方式:

  • 逻辑迁移

逻辑迁移一般只是迁移数据库表、视图及其它数据库对象,不要求源库和目标库在底层的存储及表空间完全一致。逻辑迁移适用于EC2服务器模式和RDS模式。

逻辑迁移一般使用Dump/Load+Log Apply的方式,使用Dump工具将数据库对象从源数据库导出,然后Load到目标数据库,最后根据需要同步数据库日志。

  • 物理迁移

物理迁移可以让迁移的源库和目标库在底层的存储文件、存储介质、表空间、用户等信息完全一致。物理迁移适用于EC2服务器模式。

物理迁移(Oracle)一般是使用RMan等物理备份+Log Apply的方式,使用RMan等工具备份数据库,然后在目标系统还原数据库,最后根据需要同步日志。

3.日志同步

在迁移数据库过程中,如果我们的业务有足够停机时间,可以将源数据库设置成只读数据库,然后使用Dump/Load或者备份/还原的方式来创建目标库。因为源库是只读的,迁移过程中源库不会发生变化,因此只需要根据源库数据创建目标库,无需日志的同步。

在迁移数据库过程中,如果我们的业务没有足够的停机时间,此时除了要使用Dump/Load或备份还原的方式迁移已有数据,还需要将迁移过程中变化的数据同步到目标数据库,此时需要日志同步的工具。

4.Oracle数据库同步的方法

将Oracle数据库迁移到AWS云中主要有下面几种方法:

迁移Oracle数据库有多种方式,本文主要介绍以下五种,这五种方式都是逻辑迁移:

(1)使用AWS DMS服务迁移

AWS在中国以外的区域提供了数据库迁移DMS服务,支持同构和异构数据库间的迁移,也支持日志的同步。在中国区可以使用AWS提供的DMT(Database Migration Tool)工具完成同构或异构数据库间的迁移。

DMS适合于迁移中小型的数据库。

(2)使用Oracle SQL Developer迁移

Oracle提供的SQL Developer工具里面提供了迁移功能,适合于迁移数据较少的数据库。SQL Developer可以在Oracle的官网里免费下载。

(3)使用Oracle Data Pump迁移

使用Oracle Data Pump工具将数据库导出,复制数据到目标平台,最后使用Data Pump将数据导入到目标数据库。数据量较大或数据少的库都可以使用这种方式。

(4)使用Oracle Export/Import迁移

这种方式和Oracle Data Pump方式类似,需要使用Oracle导入/导出实用工具。

(5)使用Oracle SQL Loader迁移

使用Oracle SQL Loader的方式可以让数据导入的过程更快、效率更高。

5.日志同步的方法

如果要实现不停机的迁移,就需要使用日志同步的工具,Oracle数据库支持多种不同的工具同步日志:

  • DMS同步日志

AWS的DMS服务有同步日志的选项,可以使用DMS来同步日志。

  • GoldenGate工具

可以使用Oracle的GoldenGate工具,支持同步日志到EC2上的Oracle服务器和RDS数据库。

  • 其它第三方日志复制工具

根据数据库的使用情况,我们也可以尝试其他第三方的同步工具,如SharePlex等。

6.总结

我们在将数据库从数据中心迁移到AWS云的时候,需要根据数据库的大小、业务允许的停机时间、网络的带宽等多种因素选择我们的迁移方案,每种迁移的具体步骤请参考后续博客。

作者介绍:

蓝勇

AWS解决方案架构师,负责基于AWS的云计算方案架构的咨询和设计,同时致力于AWS云服务在国内的应用和推广,在DR解决方案、数据仓库、RDS服务、企业应用、自动化运维等方面有着广泛的设计和实践经验。在加入AWS之前,在甲骨文中国担任资深售前工程师,负责售前方案咨询和架构设计,在数据库,中间件,大数据及企业应用方面有丰富经验。

手把手教你使用Amazon EMR进行交互式数据查询

本文将带您一步步完成一个利用Amazon EMR进行交互式数据查询的实例,过程包括数据的注入、数据的分析、结果的转存、以及将整个过程自动化的方法。其中涉及的EMR组件主要包括: Hive, Hadoop, presto, sqoop。除EMR外,涉及到的其他服务包括:S3, RDS. 本文所使用的数据源是cloudfront产生的日志。

在按照本文档进行操作之前,读者需了解S3,RDS并能够进行基本的S3,RDS的操作,读者需了解EMR的基本概念。以下是参考资料:

什么是EMR:

Amazon Elastic MapReduce (Amazon EMR) 是一种托管数据分析服务的框架,提升企业、研究人员、数据分析师和开发人员轻松、经济高效掌控海量数据的能力。其当前版本中托管的服务包括:Hadoop, Zeppelin, Tez, Ganglia, HBase, Pig, Hive, Presto, ZooKeeper, Sqoop, Mahout, Hue, Phoenix, Oozie, Spark, Hcatalog. EMR让您专注于数据分析,无需担心费时的集群设置、管理或调整,也无需担心所需要的计算能力。

具体参考: https://aws.amazon.com/cn/documentation/elastic-mapreduce/

什么是S3:

Amazon Simple Storage Service (Amazon S3) 为开发人员和 IT 团队提供安全、耐用且高度可扩展的对象存储。S3 可为EMR提供文件存储服务。

具体参考:https://aws.amazon.com/cn/documentation/s3/

什么是RDS:

Amazon Relational Database Service (Amazon RDS) 是一种可让用户在云中轻松设置、操作和扩展关系数据库的 Web 服务。 它在承担耗时的数据库管理任务的同时,又可提供经济高效的可调容量,使您能够腾出时间专注于应用程序开发。Amazon RDS 让您能够访问非常熟悉的 MySQL、PostgreSQL、Oracle 或 Microsoft SQL Server 等数据库引擎的功能。

具体参考:https://aws.amazon.com/cn/documentation/rds/

准备工作

1.   Cloudfront生成的日志已经存储在s3桶中,并在不断更新, 存储目录是:s3://testcloudfrontlog/log

其中testcloudfrontlog是s3存储桶的名字,在实际操作的时候,需要换一个名字,因为s3存储桶的名字是全局唯一的, 而其他人也有可能使用了这个名字。

可以从以下链接下载本例中使用的示例文件,并上传到s3://testcloudfrontlog/log目录下。

https://s3-us-west-2.amazonaws.com/hxyshare/cloudfrontlog/E36NLFLFEN3X0H.2016-07-11-02+.36b1a433.gz

https://s3-us-west-2.amazonaws.com/hxyshare/cloudfrontlog/E36NLFLFEN3X0H.2016-07-12-21.a92ddc55.gz

2.   创建一个目录来存储按照日期划分的日志数据。 目录是s3://testcloudfrontlog/logbydate

3.   创建一个目录用来做hive表的数据存储,目录是s3://testcloudfrontlog/logpart

4.   注意:直接copy本文的代码有可能会由于字符原因出现错误,建议先copy到纯文本编辑器中再执行。

手动方式完成交互式数据查询

第一步,创建一个EMR集群,方法如下:

1.   进入到AWS的控制台,选择EMR服务,点击创建集群。

2.   点击转到高级选项

3.   软件配置

选择EMR发行版本以及所需要的软件, 在本例中,我们选择emr-5.0.0版本,所需要的工具选择hadoop, hive, presto, sqoop。 本步骤中的其余选项使用默认值,然后点击下一步。如果是使用北京Region,在输入配置中输入以下内容,使得presto可访问s3, 如果使用其他Region,不必输入。

[{"classification":"presto-connector-hive", "properties":{"hive.s3.pin-client-to-current-region":"true"}, "configurations":[]}]

4.   硬件配置

进入到硬件配置界面,默认配置如下,直接使用默认配置。然后点击下一步。

5.   一般选项

在一般选项的集群名称后面输入一个名字,作为集群的名字。其余的可按照默认配置。然后点击下一步。

6.   安全选项

在EC2键对后面的框中选择一个已有的键对,该键对用来在集群创建成功后,从SSH客户端登录到集群中的任意一台服务器。如果选择“在没有EC2键对的情况下继续”,则后续不能登录到集群中的机器。其余选项均可默认。然后点击创建集群。7.   修改安全组规则,并登录EMR的主节点

进入到刚刚创建的集群的信息界面,点击主节点安全组,进入到该安全组的配置界面,在入规则中增加SSH的访问规则,这样才可以通过SSH的方式从外部机器登录到主节点。然后通过任意一个SSH客户端登录到主节点,目标地址是图中所示的主节点共有DNS, 用户名是hadoop, 通过私钥登录,私钥与前面所提到的键对对应。

第二步,创建数据表并进行查询

1.   SSH到主节点后,执行hive命令,进入到hive命令行界面

2.   创建一个用日期作为分区的hive表,用来作为最终被查询的表

将以下脚本copy到hive>提示符下执行,注意LOCATION的参数需要改成你自己的目录。

CREATE TABLE IF NOT EXISTS cloudfrontlogpart (

time STRING, xedgelocation STRING, scbytes  INT, cip STRING, csmethod STRING, csHost STRING, csuristem STRING, scstatus INT, csReferer STRING, csUserAgent STRING, csuriquery STRING, csCookie STRING, xedgeresulttype STRING, xedgerequestid STRING, xhostheader STRING, csprotocol STRING, csbytes STRING, timetaken STRING, xforwardedfor STRING, sslprotocol STRING, sslcipher STRING, xedgeresponseresulttype STRING

)

PARTITIONED BY (datee Date)

STORED AS PARQUET

LOCATION 's3://testcloudfrontlog/logpart';

3.   输入quit命令,退出hive。用aws s3命令将s3://testcloudfrontlog/log中日志copy到s3://testcloudfrontlog/logbydate中按照时间划分的目录下

以2016-07-11这一天的文件为例,命令如下,注意,s3目录需要改成你自己的。

aws s3 cp s3://testcloudfrontlog/log/  s3://testcloudfrontlog/logbydate/2016-07-11/  --exclude "*" --include "*.2016-07-11*" --recursive

这里用到了aws s3命令行工具。你可以在EMR主节点中退出hive命令行程序,然后执行以上命令。或者在任意一个安装了aws cli工具并配置了s3访问权限的机器中执行。本例中直接在EMR的主节点中执行。aws s3 cp不支持通配符,所以用–exclude 和 –include 参数来代替。

4.   针对s3://testcloudfrontlog/logbydate/2016-07-11/ 中的数据,创建一个HIVE表。

假设表的名字是cloudfrontlog20160711, 输入hive, 重新进入到hive命令行工具,并输入以下语句,注意LOCATION的参数需要改成你自己的目录。

CREATE EXTERNAL TABLE IF NOT EXISTS cloudfrontlog20160711 (

date1 Date, time STRING, xedgelocation STRING, scbytes  INT, cip STRING, csmethod STRING, csHost STRING, csuristem STRING, scstatus INT, csReferer STRING, csUserAgent STRING, csuriquery STRING, csCookie STRING, xedgeresulttype STRING, xedgerequestid STRING, xhostheader STRING, csprotocol STRING, csbytes STRING, timetaken STRING, xforwardedfor STRING, sslprotocol STRING, sslcipher STRING, xedgeresponseresulttype STRING

)

ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'

LOCATION 's3://testcloudfrontlog/logbydate/2016-07-11/'

tblproperties("skip.header.line.count"="2");

5.   数据注入

至此,已经创建了两个hive表,通过在hive命令行工具中执行 show tables命令,可以查看到两个hive表。

向带分区的hive表中注入7月11日的数据,在hive命令行界面中分别执行以下命令:

set hive.exec.dynamic.partition.mode=nonstrict;

INSERT INTO TABLE cloudfrontlogpart PARTITION (datee)

SELECT  time, xedgelocation, scbytes, cip, csmethod, csHost, csuristem, scstatus, csReferer, csUserAgent, csuriquery, csCookie, xedgeresulttype, xedgerequestid, xhostheader, csprotocol, csbytes, timetaken, xforwardedfor, sslprotocol, sslcipher, xedgeresponseresulttype, date1

FROM cloudfrontlog20160711;

以下是INSERT语句执行过程的显示信息。

完成后,如果进入到s3://testcloudfrontlog/logpart目录下,可以查看到已经生成了按日期划分的目录,目录下存储了文件。

第三步,数据查询

可以继续使用hive做查询,也可以进入presto做查询。这里,我们使用presto, 由于presto完全使用内存进行计算, 速度更快。进入presto的方式如下:

执行quit, 退出hive命令行程序。

然后执行以下语句进入presto命令行界面:

presto-cli --catalog hive --schema default

该语句表示presto使用hive数据源,并且使用hive数据源中的default数据库。细节请参考presto的社区文档。

执行一个简单的查询:

SELECT time, scbytes, cip FROM cloudfrontlogpart WHERE CAST(datee AS varchar) = CAST('2016-07-11' AS varchar) LIMIT 5;

然后退出presto命令行工具,输入 quit;

第四步,删除EMR集群

在集群列表中,选中刚刚创建的集群,点击终止,以终止该集群。如果开启的终止保护,需要变更一下终止保护的状态,然后再终止。

至此, 我们通过手动的方式完成了一个简单的数据查询。但在实际生产环境中,使用手动的方式会耗费很长时间做重复性的工作,并难免出错。EMR更大优势是能够通过程序的方式去控制集群的创建以及任务的执行,这使得EMR的使用者能够将集群创建以及数据分析的过程自动化。

接下来的部分将以相同的示例指导读者一步步的实现自动化的创建集群、分析数据、转存结果、关闭集群。

自动方式完成交互式数据查询

首先概括几点自动化执行数据分析的需求:

1.   集群在每天的固定时间被创建,然后对数据进行分析,然后集群被自动删除。这显然不能通过图形界面进行一步步的操作了。

2.   在手动操作中执行的每个步骤,需要按顺序自动化执行。这些步骤包括:

–       从/log目录向/logbydate目录中copy特定日期的文件。

–       创建对应特定日期的hive表,例如表名为cloudfrontlog20160711。

–       从cloudfrontlog20160711向cloudfrontlogpart表中注入数据。

–       使用presto从cloudfrontlogpart表中查询出需要的数据。

–       此外,在手动执行的最后一步,我们可以直接看到查询结果,但在自动化执行的过程中,我们需要将查询结果存储到一个长期运行的数据库中,供随时查询。

3.   将hive元数据放在集群的外部。

在手动执行的流程中,创建hive表后,hive的元数据存储在了主节点。而在自动化执行的过程中,当所有任务执行完毕后,集群被删除,存储在主节点的元数据也会被删除,因此要在外部数据库中存储hive的元数据。

针对以上的几个需求,在EMR中对应的解决方法如下.

1.   使用EMR的命令行进行各种集群的操作,例如集群的创建,参数的设置等。

2.   使用EMR的“step”来组织各个任务的执行。

3.   创建一个外部的数据库用来存储hive元数据,并在集群创建的时候指定元数据的存储位置。

接下来详细描述操作步骤和脚本

第一步,准备工作

1.   准备两个mysql数据库分别用来存储hive元数据和查询结果,可以使用AWS的RDS服务来创建。这两个数据库都需要能被EMR访问到, 这两个数据库也可以使用同一个物理服务器或虚拟机。

2.   假设存储查询结果的数据库名字是loganalydb,我们在该数据库中创建一个表,用来存储结果数据,表的名字是loganalytb。根据后面所进行的查询,使用如下语句创建数据库以及与查询结果匹配的表:

CREATE DATABASE loganalydb;

USE loganalydb;

CREATE TABLE `loganalytb` ( `id` int(11) NOT NULL AUTO_INCREMENT, `filepath` varchar(300) DEFAULT NULL, `totalbyte` bigint(20) DEFAULT NULL, `tdate` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=58147 DEFAULT CHARSET=utf8;

对于存储hive元数据的数据库,暂时不必创建,从后面提到的emrconf.json文件可以看到,只需要给出数据库服务器的地址,以及用户名和密码即可,数据库不存在的话,会自动创建。

3.   由于自动化的流程与前面提到的手动流程使用相同的示例,因此,请事先删除手动流程示例中产生的数据。包括:

1)   s3://testcloudfrontlog/logbydate目录下的数据

2)   s3://testcloudfrontlog/logpart目录下的数据

第二步,编写脚本

下面给出各个脚本,并配以注释解释

1.   主程序脚本loganaly.sh

#!/bin/bash

# 变量KEYNAMES是你的SSH key的名字, 用来通过SSH方式访问EC2。

KEYNAME=yourkeyname

# 变量CONFIGFILE是emrconf.json的url地址,emrconf.json这个文件中包含了存储hive元数据的外部数据库的信息。将该文件存在s3中并让这个文件能够从外部访问到。emrconf.json中的内容在下文中会给出。

CONFIGFILE=https://s3-us-west-2.amazonaws.com/testcloudfrontlog/conf/emrconf.json

# EMR集群产生的日志所存放的s3目录。

LOGURI=s3://testcloudfrontlog/emrlog/

# 脚本、配置文件、jar包所在的目录

CODEDIR=s3://testcloudfrontlog/conf

# cloudfront日志的存放位置, 结尾别加 /

LOGSOURCEDIR=s3://testcloudfrontlog/log

# 当天日志的中转目录, 结尾别加 /

STAGINGDIR=s3://testcloudfrontlog/logbydate

# 昨天的日期, 形如:20160711

DATE=$(date -d "yesterday" +%Y%m%d)

# 昨天的日期,另外一种格式, 形如:2016-07-11

DATEE=$(date -d "yesterday" +%Y-%m-%d)

# 被查询的hive表的文件存储位置, 结尾别加 /

PARTDIR=s3://testcloudfrontlog/logpart

# 用来存储结果文件的目录, 结果文件将被sqoop使用,数据会被传到mysql.

SQOOPFILE=s3://testcloudfrontlog/sqoopfile

#用来存储结果数据的mysql的信息。

DBHOST=rdsinstance.xxxxxxx.us-west-2.rds.amazonaws.com

JDBCURL=jdbc:mysql://rdsinstance.xxxxxxx.us-west-2.rds.amazonaws.com/loganalydb

DBUSER=username

DBPASS=password

# EMR集群的配置信息, 这里是以Oregan region为例。如果使用的是北京region,AWSREGION用cn-north-1。

AWSREGION=us-west-2

MASTERTYPE=m3.xlarge

CORETYPE=r3.xlarge

TASKTYPE=r3.xlarge

MASTERNUM=1

CORENUM=1

TASKNUM=1

# 删除当天的数据,目的是防止脚本在同一天被多次执行而造成数据冗余,

aws s3 rm $PARTDIR/datee=$DATEE --recursive

aws s3 rm $SQOOPFILE/$DATE --recursive

mysql -h$DBHOST -u$DBUSER -p$DBPASS --execute "DELETE FROM loganalydb.loganalytb WHERE tdate='$DATEE'"

 

# 创建emr集群,名字是loganaly, 其中:

# --auto-terminate参数表示该集群在执行完所有的任务后自动删除。

# --configurations参数的文件中的参数配置覆盖了该集群运行起来后的默认参数配置。在本例中用来修改Hive元数据的存储位置。

# --step参数规定了该集群在创建后要执行的几个任务,其中Type=Hive的step, 需要给出包含hive语句的文件作为参数。而Type=CUSTOM_JAR的step,需要给出一个JAR包,这里我们使用EMR提供scrip-runner.jar,它的作用是执行其第一个参数中指定的脚本文件,并将其余的参数作为脚本文件的输入参数。

# --instance groups参数规定了集群的中各节点的机型和数量

aws --region $AWSREGION emr create-cluster --name "loganaly" --release-label emr-5.0.0 \

--applications Name=Hadoop Name=Hive Name=Presto Name=Sqoop \

--use-default-roles \

--ec2-attributes KeyName=$KEYNAME \

--termination-protected \

--auto-terminate \

--configurations $CONFIGFILE \

--enable-debugging \

--log-uri $LOGURI \

--steps \

Type=CUSTOM_JAR,Name="cpjar",Jar=$CODEDIR/script-runner.jar,Args=["$CODEDIR/cpjar.sh"," $CODEDIR"] \

Type=CUSTOM_JAR,Name="log2staging",Jar=$CODEDIR/script-runner.jar,Args=["$CODEDIR/log2logbydate.sh","$LOGSOURCEDIR","$STAGINGDIR","$DATE","$DATEE"] \

Type=Hive,Name="HiveStep",Args=[-f,$CODEDIR/hivetables.q,-d,PARTDIRh=$PARTDIR,-d,STAGINGDIRh=$STAGINGDIR,-d,DATEh=$DATE] \

Type=CUSTOM_JAR,Name="Presto2s3",Jar=$CODEDIR/script-runner.jar,Args=["$CODEDIR/presto2s3.sh","$SQOOPFILE","$DATE","$DATEE"] \

Type=CUSTOM_JAR,Name="s3tomysql",Jar=$CODEDIR/script-runner.jar,Args=["$CODEDIR/s3tomysql.sh","$JDBCURL","$DBUSER","$DBPASS","$SQOOPFILE","$DATE"] \

--instance-groups \

Name=Master,InstanceGroupType=MASTER,InstanceType=$MASTERTYPE,InstanceCount=$MASTERNUM \

Name=Core,InstanceGroupType=CORE,InstanceType=$CORETYPE,InstanceCount=$CORENUM \

Name=Task,InstanceGroupType=TASK,InstanceType=$TASKTYPE,InstanceCount=$TASKNUM

2.   准备cpjar.sh脚本

作用是将mysql的JDBC驱动包下载到本地的Sqoop目录下,sqoop在将文件转存到数据库的时候会用到JDBC驱动。

在国外region, 使用以下脚本:

#!/bin/bash

CODEDIR=$1

sudo aws s3 cp $CODEDIR/mysql-connector-java-5.1.38-bin.jar /usr/lib/sqoop/lib/

在北京Region, 使用以下脚本:

#!/bin/bash

CODEDIR=$1

sudo aws s3 cp $CODEDIR/mysql-connector-java-5.1.38-bin.jar /usr/lib/sqoop/lib/ --region cn-north-1

3.   准备log2logbydate.sh脚本

做用是将原始日志文件copy到按天划分的目录下。

#!/bin/bash

LOGSOURCEDIR=$1

STAGINGDIR=$2

DATE=$3

DATEE=$4

aws s3 cp $LOGSOURCEDIR/ $STAGINGDIR/$DATE/ --exclude "*" --include "*.$DATEE*" --recursive

4.   准备hivetables.q脚本

作用包括:创建待查询的表和按天命名的临时表,将临时表中数据注入到待查询的表中,并删除临时表。

CREATE TABLE IF NOT EXISTS cloudfrontlogpart (

time STRING, xedgelocation STRING, scbytes  INT, cip STRING, csmethod STRING, csHost STRING, csuristem STRING, scstatus INT, csReferer STRING, csUserAgent STRING, csuriquery STRING, csCookie STRING, xedgeresulttype STRING, xedgerequestid STRING, xhostheader STRING, csprotocol STRING, csbytes STRING, timetaken STRING, xforwardedfor STRING, sslprotocol STRING, sslcipher STRING, xedgeresponseresulttype STRING

)

PARTITIONED BY (datee Date)

LOCATION '${PARTDIRh}';

 

CREATE EXTERNAL TABLE IF NOT EXISTS cloudfrontlog${DATEh} (

date1 Date, time STRING, xedgelocation STRING, scbytes  INT, cip STRING, csmethod STRING, csHost STRING, csuristem STRING, scstatus INT, csReferer STRING, csUserAgent STRING, csuriquery STRING, csCookie STRING, xedgeresulttype STRING, xedgerequestid STRING, xhostheader STRING, csprotocol STRING, csbytes STRING, timetaken STRING, xforwardedfor STRING, sslprotocol STRING, sslcipher STRING, xedgeresponseresulttype STRING

)

ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'

LOCATION '${STAGINGDIRh}/${DATEh}/'

tblproperties("skip.header.line.count"="2");

 

--make dynamic insert available

set hive.exec.dynamic.partition.mode=nonstrict;

 

--insert data.

INSERT INTO TABLE cloudfrontlogpart PARTITION (datee)

SELECT  time, xedgelocation, scbytes, cip, csmethod, csHost, csuristem, scstatus, csReferer, csUserAgent, csuriquery, csCookie, xedgeresulttype, xedgerequestid, xhostheader, csprotocol, csbytes, timetaken, xforwardedfor, sslprotocol, sslcipher, xedgeresponseresulttype, date1

FROM cloudfrontlog${DATEh};

 

--delete the staging table

DROP TABLE cloudfrontlogstaging${DATEh};

5.   准备presto2s3.sh脚本

作用包括:查询hive表,并将结果存成.csv格式的文件,将该文件上传到S3中相应的目录下。

#!/bin/bash

TIME=$(date +%H%M%S)

SQOOPFILE=$1

DATE=$2

DATEE=$3

sudo presto-cli --catalog hive --schema default --execute "SELECT NULL as id, csuristem, SUM(scbytes) as totalbyte, CAST('$DATEE' AS varchar) as date FROM cloudfrontlogpart WHERE CAST(datee AS varchar) = CAST('$DATEE' AS varchar) GROUP BY csuristem order by totalbyte desc" --output-format CSV > ~/cdnfilestat.csv

aws s3 cp ~/cdnfilestat.csv $SQOOPFILE/$DATE/cdnfilestat$TIME.csv

6.   准备s3tomysql.sh脚本

作用是利用sqoop将s3中的.csv文件中的内容转存到数据库中。注意,如果单次转存的数据量大,你可能需要调大数据库的max_allowed_packet参数。

#!/bin/bash

JDBCURL=$1

DBUSER=$2

DBPASS=$3

SQOOPFILE=$4

DATE=$5

sqoop export --connect $JDBCURL --username $DBUSER --password $DBPASS --table loganalytb --fields-terminated-by ',' --enclosed-by '\"' --export-dir $SQOOPFILE/$DATE/

7.   准备emrconf.json脚本

作用是使得创建起来的EMR集群的hive元数据存储在外部数据库中。注意:根据准备工作中创建的数据库来修改文件中的ConnectionUserName、ConnectionPassword、ConnectionURL三个参数。

[

{"Classification":"hive-site",

"Properties":{

"javax.jdo.option.ConnectionUserName":"username",

"javax.jdo.option.ConnectionDriverName":"org.mariadb.jdbc.Driver",

"javax.jdo.option.ConnectionPassword":"password",

"javax.jdo.option.ConnectionURL":"jdbc:mysql://rdsinstance.xxxxxxxxxx.us-west-2.rds.amazonaws.com:3306/hive?createDatabaseIfNotExist=true"

},

"Configurations":[]

}

]

注意:如果是在北京Region, emrconf.json使用以下脚本

[

{"Classification":"hive-site",

"Properties":{

"javax.jdo.option.ConnectionUserName":"username",

"javax.jdo.option.ConnectionDriverName":"org.mariadb.jdbc.Driver",

"javax.jdo.option.ConnectionPassword":"password",

"javax.jdo.option.ConnectionURL":"jdbc:mysql://rdsinstance.xxxxxxxx.us-west-2.rds.amazonaws.com:3306/hive?createDatabaseIfNotExist=true"

},

"Configurations":[]

},

{"Classification":"presto-connector-hive",

"Properties":{

"hive.s3.pin-client-to-current-region":"true"

},

"Configurations":[]

}

]

8.   准备jar包

需要两个jar,一个是script-runner.jar,下载地址: http://s3.amazonaws.com/elasticmapreduce/libs/script-runner/script-runner.jar

另一个是mysql的JDBC驱动,下载地址: https://s3-us-west-2.amazonaws.com/hxyshare/mysql-connector-java-5.1.38-bin.jar

9.   上传文件

在s3中创建s3://testcloudfrontlog/conf/目录,并将第8步中的两个jar包,以及cpjar.sh,log2staging.sh,hivetables.q,presto2s3.sh,s3tomysql.sh,emrconf.json,上传到该目录。

由于emrconf.json需要通过http的方式访问到,在s3中将emrconf.json的访问权限增加“所有人可下载”。

cloudfront日志文件copy到s3://testcloudfrontlog/log目录下, 两个示例文件下载地址:

https://s3-us-west-2.amazonaws.com/hxyshare/cloudfrontlog/E36NLFLFEN3X0H.2016-07-11-02+.36b1a433.gz

https://s3-us-west-2.amazonaws.com/hxyshare/cloudfrontlog/E36NLFLFEN3X0H.2016-07-12-21.a92ddc55.gz

如果在手动流程中已经上传了日志文件,则不必再上传。

第三步,执行程序

暂时将主程序loganaly.sh中的DATE和DATEE参数修改为示例数据的时间,例如,分别写成20160711和2016-07-11,在任意一个Linux系统中运行主程序脚本loganaly.sh(例如,可以使用EC2实例)。但需注意:

1) 该机器需要安装了AWS命令行工具,并具有s3和EMR的操作权限。

2) 该机器能访问到存储数据结果的数据库,因为loganaly.sh中有对该数据库的操作。

集群创建成功后,自动执行每个step中的任务,所有任务执行完成后自动关闭,从下图中可以看到每个Step的执行:

所有任务执行完成后,进入到存储查询结果的数据库,查看输入的结果:

如果要想每天执行loganaly.sh脚本并对前一天的数据进行处理和分析,将loganaly.sh中的DATE和DATEE分别赋值为 $(date -d “yesterday” +%Y%m%d) 和 $(date -d “yesterday” +%Y-%m-%d),然后创建一个crontab定时任务,每天定时执行loganaly.sh.

如果是在AWS中国以外的region执行,还可以利用竞价实例来大幅的降低成本,使用竞价实例的方法也非常简单,只需要在将loganaly.sh中对创建EMR集群的脚本稍做修改,增加BidPrice参数:

--instance-groups \

Name=Master,InstanceGroupType=MASTER,InstanceType=$MASTERTYPE,BidPrice=0.2,InstanceCount=$MASTERNUM \

Name=Core,InstanceGroupType=CORE,InstanceType=$CORETYPE,BidPrice=0.2,InstanceCount=$CORENUM \

Name=Task,InstanceGroupType=TASK,InstanceType=$TASKTYPE,BidPrice=0.2,InstanceCount=$TASKNUM

第四步,删除资源

为避免额外花费,删除本实验过程中(包括手动过程以及自动过程中)创建的资源,S3, EC2等资源。

总结

本文中使用Cloudfront日志进行分析,但本文中使用的方法稍作修改便适用于其他类型的日志类文件的分析。本文中主要使用了EMR中的Hive,Presto,Sqoop工具,但EMR还有更多的工具(例如Spark)可供用户使用,用户在创建集群的时候增加相应的服务即可实现丰富的功能。

作者介绍:

韩小勇

AWS解决方案架构师,负责基于AWS的云计算方案架构咨询和设计,实施和推广,在加入AWS之前,从事电信核心网系统上云的方案设计及标准化推广 。

手把手教你如何用Lambda + Alexa调用echo设备

知识补充:

什么是AWS Lambda?

AWS Lambda在可用性高的计算基础设施上运行您的代码,执行计算资源的所有管理工作,其中包括服务器和操作系统维护、容量预置和自动扩展、代码监控和记录,只在需要时执行您的代码并自动缩放,从每天几个请求到每秒数千个请求,其提供了AWS基础设施的高可用性,高安全性,高功能性和高可扩展性。

具体可参考:

https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/welcome.html

什么是Alexa Skills Kit?

Alexa是Echo内置的语音助手,通过它能够唤醒Echo。Alexa的优点在于,它基于云端,因此我们可以随时对其进行改进。Alexa Skills Kit (ASK)是一个由自服务API、工具、文件和实例代码的集合,可轻松构建你自定义的Alexa skills,然后发布。

具体可参考:

https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit

1. 打开链接https://aws.amazon.com/,申请亚马逊AWS账号。登录控制台,选择AWS Lambda服务,创建Lambda Function。

2. 选择Alexa Skills Kit

3. 下载需要用到的代码,解压,打开index.js文件,修改文件中的开发者账号ID,如下:

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/RecipeTemplate.zip

修改完成之后,然后打成Zip包上传(注意,这里的打包不需要文件夹,直接把.js文件打包成RecipeTemplate.zip)

接着点击“Create function”

到这里,Lambda 创建成功。

4. 进入https://developer.amazon.com/,创建Alexa Skills Kit。

选择ALEXA

5. 选择“Alexa Skills Kit”

6. 点击“Add a new Skill”

7. 填写Name: Solution Helper,Invocation Name: solution helper

8. 填写Intent Schema,样例代码下载:

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/IntentSchema.json

9. 填写Sample Utterances,样例代码下载:

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/SampleUtterances.txt

10. 点击“Save”后会报错,这个时候点击“Add Slot Type”添加。样例代码下载

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/LIST_OF_ITEMS

11. 然后再点击“保存”,这个时候看到Successfully updated the interaction model

12. 此时,点击“Next”,选择Lambda的ARN地址,例如:

arn:aws:lambda:us-east-1:556776719183:function:LambdaAlexaSkillsKit

复制Lambda ARN地址

13. 点击“Next”,即将进入到Test阶段,如下图所示。

14. 然后在Enter Utterance中输入“How can I build a map”,点击“Ask Minecraft Helper”,能看到Lambda Response的结果。

15.(选做)如果是测试用的话这步可以不进行。

接下去会上传两张图片,这两张图片是特定尺寸的,而且是必须要上传的,你可以自定义。或者用两张我已经做好的图片:

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/AWS+Logo+108.png

https://s3.cn-north-1.amazonaws.com.cn/bjsdemo/LambdaAlexaSkillsKit/AWS+Logo+512.png

16.(选做)如果是测试用的话这步可以不进行。

17. 使用Web版Alexa Skill Testing Tool进行测试 https://echosim.io/,注意,这里需要用之前的Amazon账号登录。用鼠标点击进行语音。

18. 当输入语音之后,打开Alexa的测试页面,点击Home,可以看到录音识别的效果。http://alexa.amazon.com/spa/index.html#cards

比如我语音输入:

Alexa, Ask solution helper how can build a map

Echo回答:

A map can be crafted by placing a compass in the middle square and eight pieces of paper surrounding it.


也可以进行自定义语音设置,比如我语音输入:

Alexa, Ask solution helper how can I get summit ticket

Echo回答:

Hello, if you want to attend beijing summit, please connect to aws china inside sales team.

19. 搞定,完成!

作者介绍:

毛郸榕

亚马逊AWS中国助理解决方案架构师,负责基于AWS的云计算方案架构的咨询和设计,同时致力于AWS云服务在国内的应用和推广,毕业于北京航空航天大学云计算专业,硕士,毕业后直接加入亚马逊AWS中国。在大规模后台架构、企业混合IT和自动化运维等方面有着丰富的实践经验。目前在集中精力学习新一代无服务器架构设计。

AWS Kinesis的Javascript交互方法

一.介绍

Amazon Kinesis 是一种托管的弹性可扩展服务,能实时处理大规模的流数据。在该服务收集大数据记录流后,可用多种数据处理应用程序实时处理该数据流。Amazon Kinesis Streams 每小时可从数十万种来源中连续捕获和存储数 TB 数据,如网站点击流、财务交易、社交媒体源、IT 日志和定位追踪事件。Amazon Kinesis Streams 数据流的吞吐量每小时可从数 MB 扩展到数 TB,PUT 记录每秒钟可从数千次扩展到数百万。您可以随时根据您的输入数据量动态调节数据流的吞吐量。

AWS为旗下的服务提供了多种开发工具包,支持包括Java、PHP、Python、Ruby、浏览器端等语言或平台。对于Amazon Kinesis,我们除了使用上述的Stream API进行开发外,AWS还提供了Amazon Kinesis Client Library (KCL) 开发适用于 Amazon Kinesis Streams 的使用器应用程序。在本文当中,我们展示如何使用Javascript在浏览器端与Amazon Kinesis进行交互,包括把记录Put到Kinesis,和从Kinesis读取记录。

二.基本概念与限制

在阐述如何AWS Kinesis的Javascript交互方法前,我们有必要对Kinesis Stream当中的关键概念——“分片”和“数据记录”作初步的了解。

分片

分片Share是流中数据记录的唯一标识组。一个流由一个或多个分片组成,每个分片提供一个固定的容量单位。流的总容量是其分片容量的总和。每个分片对应提供 1 MB/s 的写入容量和 2 MB/s 的读取容量。需要注意的是,每个分片可支持最多1000条记录/s的写入,和5个事务/s的读取。用户需要根据上述的容量和数目的限制,为流添加足够多的分片数目,以满足自身需求。

数据记录

数据记录是存储在 Amazon Kinesis Stream中的数据单位。数据记录由序列号、分区键和数据 Blob 组成。

每个数据记录都有一个唯一的序列号。当应用程序对Amazon Kinesis Stream进行写入记录时,Streams将自动为其分配序列号。同一分区键的序列号通常会随时间变化增加;写入请求之间的时间段越长,序列号则越大。但需要注意的是,序列号不能用作相同流中的数据集的索引。用户如果需要在逻辑上分隔数据集,请使用分区键或为每个数据集创建单独的流。

分区键Partition Key用于按分片对流中的数据进行分组。Streams 服务使用与每条数据记录关联的分区键将属于流的数据记录分为多个分片,以便确定给定的数据记录所属的分片。分区键是最大长度限制为 256 个字节的 Unicode 字符串。MD5 哈希函数用于将分区键映射到 128 位整数值并将关联的数据记录映射到分片。分区键由将数据放入流的应用程序指定。

Blob是不可变的字节序列,也就是用户添加到Kinesis Stream中真正存储的数据。Streams 不以任何方式检查、解释或更改 Blob 中的数据。数据 Blob 可以是任何类型的数据,例如可以为日志文件的一个分段、地理位置数据、网页点击流数据等等。一个Blob 数据最多为 1 MB。

关于Amazon Kinesis Streams更详细概念信息,请查看 以下AWS官方文档:http://docs.aws.amazon.com/zh_cn/kinesis/latest/dev/service-sizes-and-limits.html

三.安全性问题

要对AWS Kinesis发送或接收消息,我们需要在客户端中设置安全证书,才能与Kinesis进行交互。用户可以选择在客户端中使用固定的IAM证书或者临时证书,但如果您选择使用固定的IAM证书,请注意当中涉及到的重大安全性问题!客户能够轻易地从前端页面获取IAM用户的AWS Access Key ID和AWS Secret Access Key,而且固定的IAM证书长期有效,如果大部分用户共用同一个IAM证书,极容易发生恶意攻击的情况。因此,尽管技术上可行,但我们不建议用户使用固定的IAM证书,强烈建议用户使用临时证书!用户能够给临时证书设定较短的有效期,而且每次申请所获得临时证书都是独一无二的。用户能够在其自身的恶意攻击检测中直接让攻击源的临时证书失效,而不影响其他客户的正常使用。

在本文档中,我们将会介绍使用TVM服务器获取临时证书以及使用AWS的托管服务Cognito获取临时证书两种方法。但无论使用TVM还是Cognito,共同的目的都是获取临时证书的accessKeyId、secretAccessKey、sessionToken三个参数。因此获取临时证书是本文相对独立的一部分,我们会在第八部分和第九部分分别进行介绍。而在第七部分,我们假设前端已经获取上述临时证书三个参数的情况下,对Kinesis的Javascript交互方法进行讲述。

四.准备资源文件

要使用Javascript访问AWS Kinesis,需要准备AWS的Javascript开发工具包。AWS的Javascript开发工具包可从AWS官网上下载,或在前端页面的head部分直接引用该js包。在本篇文章撰写时,最新js包的地址为https://sdk.amazonaws.com/js/aws-sdk-2.4.13.min.js。您可以关注该工具包的github项目地址(https://github.com/aws/aws-sdk-js),随时获取最新的开发工具包。

五.准备AWS账户及创建AWS Kinesis Stream

要使用Javascript与AWS Kinesis进行交互,我们需要在云端创建一个可用的AWS Kinesis Stream。如今,包括中国北京在内的AWS Region都支持使用Kinesis服务,因此,用户创建AWS中国区账户或AWS标准账户都能够使用AWS Kinesis服务。

进入AWS账户后,我们需要在AWS Kinesis服务中创建流。具体的创建方法为:

  1. 登陆AWS账户终端界面,点击Kinesis操作模块 https://console.aws.amazon.com/kinesis
  2. 在操作模块中点击“创建流”。自定义流名称后,分区数量我们填写最低配置1,然后点击“创建”按钮即可。(为了节省成本,我们这里只为流添加一个分片。用户可以根据自己的实际需要设定分片的数量)

六.权限设置

要对AWS Kinesis Stream生产和消费数据,我们需要为临时证书添加相应的权限。一般情况下,我们建议用户为临时证书提供最低的AWS Kinesis使用权限。对资源的严格限制,是我们AWS一直推崇的做法。生产者和消费者的最低权限设置分别如下所示:

生产者Producer的最低权限设置:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Allow",

            "Action": [

                "kinesis:DescribeStream",

                "kinesis:PutRecord",

                "kinesis:PutRecords"

            ],

            "Resource": [

                "arn:aws-cn:kinesis:xxxxxxxxxx:xxxxxxxxxxxx:stream/Vincent_danmu_BG"

            ]

        }

    ]

}

消费者Consumer的最低权限设置:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Effect": "Allow",

            "Action": [

                "kinesis:DescribeStream",

                "kinesis:GetShardIterator",

                "kinesis:GetRecords"

            ],

            "Resource": [

                "arn:aws-cn:kinesis:xxxxxxxxxx:xxxxxxxxxxxx:stream/Vincent_danmu_BG"

            ]

        }

    ]

}

另外,如果您使用 Amazon Kinesis Client Library (KCL) 开发应用程序,您的策略必须包含对 Amazon DynamoDB 和 Amazon CloudWatch 的权限。因为KCL需要使用 DynamoDB 跟踪应用程序的状态信息,并使用 CloudWatch 代表您将 KCL 指标发送到 CloudWatch。如果用户并非使用KCL 开发应用程序,包括本次的Kinesis与Javascript交互演示,无需使用DynamoDB和CloudWatch资源,在此仅作提示。在KCL的场景下需要增加的权限如下所示:

  {

            "Effect": "Allow",

            "Action": [

                "dynamodb:CreateTable",

                "dynamodb:DeleteItem",

                "dynamodb:DescribeTable",

                "dynamodb:GetItem",

                "dynamodb:PutItem",

                "dynamodb:Scan",

                "dynamodb:UpdateItem"

            ],

            "Resource": [

                "arn:aws-cn:dynamodb:xxxxxxxxxx:xxxxxxxxxxxx:table/amazon-kinesis-learning"

            ]

        },

        {

            "Effect": "Allow",

            "Action": [

                "cloudwatch:PutMetricData"

            ],

            "Resource": [

                "*"

            ]

        }

需要注意的是,上述红色标注的地方为本人所使用的AWS Kinesis Stream和 DynamoDB表格的ARN资源名称(Stream对应的名称为Vincent_danmu_BG,DynamoDB table对应的名称为amazon-kinesis-learning),用户需要把它替换成自己个人所对应的ARN资源。关于如何寻找各AWS服务中对应的资源名称,请参考AWS官方网站说明。另外需要注意的是,如果ARN资源在中国区内,一般需要以“arn:aws-cn:”开头标注资源;如果ARN资源在中国区外的标准AWS区域,则只需要使用一般的“arn:aws:”开头标注资源。

更多关于配置AWS Kinesis的权限问题,请参考AWS官方文档:http://docs.aws.amazon.com/streams/latest/dev/learning-kinesis-module-one-iam.html。文档当中详细叙述了在AWS Console的IAM模块中如何创建User和Policy,为初学者提供很好的帮助。

七.在Javascript中对AWS Kinesis的访问方法

经过上述的准备工作,我们在这部分中,将会对AWS Kinesis的Javascript交互方法的核心部分进行介绍。

基本参数配置

无论是推送消息到AWS Kinesis还是从AWS Kinesis接收消息,我们都需要在Javascript中传入基本参数配置信息,包括临时证书的accessKeyId、secretAccessKey、sessionToken以及Kinesis Stream所在的Region、Kinesis Stream名称、分区键等等。然后我们需要用这些基本参数配置信息初始化这个过程中最重要的操作对象AWS.Kinesis。为了方便起见,我们建议用户把这些公用配置操作写在同一个文件当中,代码如下。

// constants of AWS Kinesis

var accessKeyId = '';//在这里传入临时证书的accessKeyId

var secretAccessKey = '';//在这里传入临时证书的secretAccessKey

var sessionToken = ''; //在这里传入临时证书的sessionToken

var region = '';//在这里输入AWS Region

var stream_name = '';//在这里输入Kinesis的流名称

var partition_key = '';//请在这里输入分区键

//初始化kinesis对象

var kinesis = new AWS.Kinesis({accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, sessionToken: sessionToken, region: region });

在这里需要注意的是,我们初始化AWS.Kinesis对象时只需要传入accessKeyId、secretAccessKey、sessionToken、region四个参数。stream_name和partition_key会在后续推送消息或接收消息调用相关函数的时候作为参数传入。

推送消息到AWS Kinesis

要向Kinesis中推送消息,我们需要调用aws-sdk-2.4.13.min.js中的putRecord方法。putRecord方法每次向Kinesis Stream中推送一条record,AWS同时也支持API putRecords方法,用于每次向Kinesis Stream中推送多条record,读者可自行查看AWS API文档尝试。作为一种良好的代码写作习惯,我们把这部分的功能封装在函数function putStream(text)中。具体代码如下所示:

//该方法用于推送单条信息到Kinesis

function putStream(text) {

  var write_simple_params = {

    Data: text,

    PartitionKey: partition_key,

    StreamName: stream_name

  };

  kinesis.putRecord(write_simple_params, function(err, data) {//推动单条消息到Kinesis并定义返回函数

    if (err){// an error occurred

      console.log(err, err.stack);

    }else{

      alert('成功发送消息!');

    }

  });

}

需要注意的是,调用putRecord方法时需要传入配置参数组(这里为write_simple_params)和设置好回调函数。参数组的组成包括Blob的内容Data、分区键PartitionKey、流名称StreamName。通过回调函数,我们能够检测是否成功地把数据发送到Kinesis,并根据该结果进行下一步的操作。开发人员可以通过err判断是否产生错误,并通过err.stack获取错误产生的原因。

从AWS Kinesis接收消息

相比于向Kinesis推送消息,从Kinesis接收消息的流程和步骤相对复杂一点点。首先我们需要用getShardIterator方法获取初始分片迭代器ShardIterator。然后用ShardIterator从Kinesis获取记录。在这个过程当中,为了确保更新得以持续,我们循环自调用该方法,以保证ShardIterator可用。同样地,我们把这部分的功能封装在函数function readStream()中。具体关键代码如下所示:

//在第一次getShardIterator中获取ShardIterator读取数据

 function readStream(){

    var shardIterator_params = {

      ShardId: 'shardId-000000000000',

      ShardIteratorType: 'LATEST',

      StreamName: stream_name,

    }

    kinesis.getShardIterator(shardIterator_params, function(err, data) {

        if (!err) {

            readStreamLoop(data.ShardIterator);//第一次成功获取ShardIterator后进入循环更新以不断获取新的ShardIterator

        }else{

            console.log(err, err.stack);

            alert("ShardIterator Error");

        }

    });

 }

需要注意的是,调用getShardIterator方法时需要传入配置参数组(这里为shardIterator_params)和设置好回调函数。配置参数组需要我们输入分片Id号ShardId、ShardIterator的排序类型ShardIteratorType和流名称StreamName三项。同样地,通过回调函数我们能够检查存在的错误,并根据结果进行下一步的操作。在回调函数中,如果没有出现错误,我们可以通过data.ShardIterator获取初始分片迭代器的Id,然后凭借该Id从Kinesis获取记录。获取记录的具体步骤我们封装在函数function readStreamLoop(shardIteratorId)当中,具体代码如下所示。

//不断重复地获取下一个ShardIterator从而达到实时获取数据的效果。但该循环会不断消耗CPU性能

 function readStreamLoop(shardIteratorId) {

    var self = this;

    var read_params = {

        ShardIterator: shardIteratorId,

        Limit: 10

    };

    kinesis.getRecords(read_params, function(err, data) {

        if (!err){

            if (data['Records'].length > 0) {

                for (var i in data['Records']) {

                    msg = data['Records'][i]['Data'].toString('utf8');

                }

            }

            self.readStreamLoop(data['NextShardIterator']);//获取下一个ShardIterator的Id后不断自调用

        }

    });

}

需要注意的是,由于AWS Kinesis在接收数据后并不会主动向应用程序推送数据,因此我们需要在客户端不断地发出请求以达到实时获取数据的目的。为了达到这个效果,我们在上述代码中通过self.readStreamLoop(data[‘NextShardIterator’])不断地自调用readStreamLoop(shardIteratorId)方法进行实现。当然,如果用户对获取数据的实时性要求不高,完全可以通过一些定时的方法(如setInterval())来实现同样的效果。无论如何,保持这段代码轻量级对于减少CPU的负荷是百利而无一害的。

调用getRecords方法时需要传入配置参数组(这里为read_params)和设置好回调函数。配置参数组需要我们输入分片迭代器Id号ShardIterator和一次读取的限制个数Limit。在回调函数中,如果没有出现错误,我们可以通过数组data[‘Records’]得到这次获取的所有记录。另外,我们能够通过data[‘NextShardIterator’]以获取下一个分片迭代器的Id,从而做到不断的持续更新。

更多AWS-SDK中关于Kinesis的API调用方法与实例,请参考以下AWS文档:http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Kinesis.html

八.自建TVM服务器使用AWS STS服务获取临时证书

在上述第三部分的安全性问题中提到,把AWS Access Key ID和AWS Secret Access Key存放在html文件或js文件中会存在较大的安全问题,而且考虑到固定的AWS IAM安全证书的定期更新、防止黑客的窃取和攻击等问题,我们需要使用AWS 安全令牌服务Security Token Service(简称STS)颁发临时证书,通过限制这些临时安全证书的AWS服务访问权限和有效时间,从而解决上述存在的安全问题。

具体如何在AWS EC2上构建TVM服务器,请参阅亚马逊AWS官方博客中的《Token Vending Machine:移动应用客户端安全访问AWS服务的解决方案》一文。

在与自建TVM服务器交互以获取临时证书的过程中,您可以在TVM程序中加入自行维护的验证保护机制,例如要求用户提供在您所维护的验证机构中的验证信息。加入验证保护机制能够使用户有条件地获取临时证书,从而更好地保护您的资源。当然,个别的应用场景,例如对于临时访客,匿名地获取临时证书也是一种客户需求。

在这里,我们将重点讲述如何用Javascript获取证书,并把该证书缓存到cookies等操作。

Javascript从TVM服务器上获取临时证书

jquery的$.ajax()是一种与服务器交换数据的技术,它可以在不刷新整个页面的情况下向服务器发出请求并获取返回的数据。我们使用$.ajax()完成客户端向TVM服务器请求临时证书的操作,整个过程做到对用户透明。我们把这一部分封装在函数function getCertification()当中,代码如下所示:

//从TVM获取临时证书

function getCertification(){

    $.ajax({

        url:'getCertification.action',//默认在当前页地址发送请求

        type:'post',

        async: false,//使用同步请求

        data:"{}",//客户端发送到服务器端的数据

        dataType:'json',//预期服务器相应的数据类型

        success:function (data) {//成功返回的回调函数

            setCertificationFromCookies(data.accessKeyId, data.secretAccessKey, data.sessionToken, data.expiration);

        }

    });

}

需要注意的是,由于我们的请求页面恰好放置在TVM服务器中,所以我们只需要默认在当前页地址发送请求即可。对于TVM服务器和当前页面请求域名不相同的情况,则需要补全完整的url地址。除此之外,我们部署的TVM服务器返回的是json格式的证书数据,返回的数据格式为标准的json格式,如下所示:

{“accessKeyId”:”(20位长字符串)“,”secretAccessKey”:”(40位长字符串)“,”sessionToken”:”(712位长字符串)“,”expiration”:(从1970年1月1日到现在的毫秒数)}

因此,在$.ajax()成功返回的回调函数中,我们只需要用“data.关键字”就能够分别把上述四个证书属性提取出来。当这四个属性提取出来后,我们把它们分别作为函数function setCertificationFromCookies(accessKeyId, secretAccessKey, sessionToken, expiration)的四个参数,把它们保存到cookies当中。该函数具体代码如下所示:

//把证书写到Cookies当中

function setCertificationFromCookies(accessKeyId, secretAccessKey, sessionToken, expiration){

    var date = new Date(expiration);

    $.cookie('accessKeyId', accessKeyId, { expires: date, path: '/' });

    $.cookie('secretAccessKey', secretAccessKey, { expires: date, path: '/' });

    $.cookie('sessionToken', sessionToken, { expires: date, path: '/' });

    $.cookie('expiration', expiration, { expires: date, path: '/' });

}

可以看到,我们把证书的过期时间作为cookies的过期时间。

另外,我们在运行的过程中还需要检查该证书是否已经过期,具体代码如下:

//检查证书是否过期

function checkExpiration(){

    if($.cookie('expiration') == null || new Date() > new Date(parseInt($.cookie('expiration')))){

        return false;

    }else{

        return true;

    }

}

需要注意的是,由于临时证书的有效期限较短,因此建议在每次使用kinesis对象前,都需要把临时证书的expiration和当前的时间做对比,以防止临时证书的过期而导致的kinesis对象方法调用的失败。具体的代码如下:

//检查当前的Kinesis是否可用。原则上在每次使用Kinesis之前都应该检查一遍。为了加快相应速度,应该在页面加载的时候运行之以达到预加载证书的效果

function checkKinesis(){

    if(!checkExpiration()){

        getCertification();

        initKinesis();

    }else if(sessionToken == null){

        initKinesis();

    }

}

另外,为了减轻TVM服务器的负载,我们建议从TVM获取临时安全证书后,把临时安全证书保存在客户端当中,在有效期内重复使用。您可以选在Javascript变量或者cookies等方式进行保存。在上述说明中,我们使用cookies的方式对之进行保存。

九.使用AWS的托管服务Cognito获取临时证书

Amazon Cognito 是专为想要将用户管理和同步功能添加到其移动和 Web 应用程序的开发人员设计的。Cognito分为Federated Identities和User Pools两部分。Federated Identities能够让您创建一个用户身份认证服务,通过认证可以给客户颁发临时的、自定义权限的证书。而User Pools是一个可以安全存储用户资料属性的用户目录,您可以把包括登录和注册信息在内的用户信息保存在User Pools中统一管理。

类似于在自建TVM服务器中自行选择提供验证保护机制,Federated Identities允许用户提供认证和未认证两种方式,有差异地提供不同权限的临时证书。

在本节中,我们假设在某一个User Pool中保存了用户信息,并把该User Pool作为Identity的Authentication  provider。另外,我们在该Identity的Authenticated role中赋予上述第六部分的访问Kinesis的最低权限。这样从该Identity所获取到的临时证书就拥有访问Kinesis的权限。关于如何创建及配置identity pools和user pools,请参考AWS的官方文档(http://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/what-is-amazon-cognito.html)和AWS的官方微博(https://aws.amazon.com/cn/blogs/aws/category/amazon-cognito/)。

另外,当我们使用Javascript向User Pool认证身份的时候,要正常调用该API,需要使用jsbn.js、jsbn2.js、sjcl.js、moment.min.js、aws-cognito-sdk.min.js、amazon-cognito-identity.min.js等第三方或AWS额外的js包,具体的js包依赖及下载地址请查看AWS官方文档 http://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/setting-up-the-javascript-sdk.html

一切就绪后,我们就能够在前端通过User Pools认证身份获取用户的IdToken,然后用该IdToken向Identity Pools获取临时证书的accessKeyId、secretAccessKey、sessionToken。凭借该临时证书,我们就能够通过上述的第七部分访问Kinesis。具体从Cognito获取临时证书的javascript方法封装在下面的getAuthenticatedIdentity(username, password)方法当中:

//获取登录认证的临时证书

function getAuthenticatedIdentity(username, password){//传入参数为User Pools中的用户名和密码

    AWSCognito.config.region = 'us-west-2'; //所使用的Cognito服务的Region,这里假设为us-west-2

    var poolData = {

        UserPoolId : 'us-west-2_xxxxxxxxx',//填写User Pool的id

        ClientId : 'xxxxxxxxxxxxxxxxxxxxxxxxxx'//填写User Pool中对应App的client id

    };

    var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);

    var authenticationData = {

        Username : username,

        Password : password

    };

    var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);

    var userData = {

        Username : username,

        Pool : userPool

    };

    var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);

    cognitoUser.authenticateUser(authenticationDetails, {

        onSuccess: function (result) {

            // Set the region where your identity pool exists

            AWS.config.region = 'us-west-2'; //所使用的Cognito服务的Region,这里假设为us-west-2

            // Configure the credentials provider to use your identity pool

            AWS.config.credentials = new AWS.CognitoIdentityCredentials({

                IdentityPoolId: 'us-west-2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', //填写Identity Pool的id

                Logins: {

                    'cognito-idp.us-west-2.amazonaws.com/us-west-2_r12hRvjCr': result.getIdToken().getJwtToken()

                }

            });

            // Make the call to obtain credentials

            AWS.config.credentials.get(function(){

                // Credentials will be available when this function is called.

                accessKeyId = AWS.config.credentials.accessKeyId;

                secretAccessKey = AWS.config.credentials.secretAccessKey;

                sessionToken = AWS.config.credentials.sessionToken;

            });

        },

        onFailure: function(err) {

            alert(err);

        }

    });

}

需要注意的是,我们需要从前端把客户的用户名和密码传入到该js方法当中。cognitoUser根据该用户名、密码等参数从User Pool获取该客户的IdToken。特别地,该IdToken的有效期为一小时。当IdToken失效后,用户可通过RefreshToken获取新的IdToken。用户可以在创建User Pool的app的时候自定义RefreshToken的有效期(1~3600天,默认30天)。另外,从Identity Pool获取的临时证书有效期为一小时。同样地,用户可以通过Javascript变量或者cookies等方式保存临时证书,以达到复用的效果。

另外,在上述的红色代码中,我们特别需要注意是Provider Name的格式为:cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>,而Value值为User Pool ID。具体如何把Identity Pool和User Pool集成使用,可参考AWS的官方文档 http://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html

十.总结

AWS Kinesis服务能够在短时间内实现弹性扩展,帮助您实时处理大规模的流数据。而且Kinesis服务还极其灵活,能够从任何可以调用Web服务的地方收集数据。本文介绍了如何通过Javascript对Kinesis进行交互,除此之外,用户还能够通过AWS所提供的丰富的开发工具包实现兼容各种语言和平台的Kinesis交互客户端,使用户能够从多个渠道收集数据。

既然一切都准备就绪,那就出发吧,详细AWS Kinesis服务会给你带来不一样的惊喜。Good Luck!

作者介绍:

邓明轩

亚 马逊AWS解决方案架构师;拥有15年IT 领域的工作经验,先后在IBM,RIM,Apple 等企业担任工程师、架构师等职位;目前就职于AWS,担任解决方案架构师一职。喜欢编程,喜欢各种编程语言,尤其喜欢Lisp。喜欢新技术,喜欢各种技术 挑战,目前在集中精力学习分布式计算环境下的机器学习算法。

邱越俊

亚马逊AWS解决方案架构师实习生,擅长Web开发,熟悉使用Java、Javascript、Html5、Mysql数据库,曾在多个互联网公司从事软件平台开发工作,对计算机网络架构、云平台的开发和部署有一定的经验。

 

算法改变世界 - 从Prisma 的走红说开去

假设你是一个摄影爱好者,估计最近这几天应该正忙着用一款热门的手机App去修图并晒到朋友圈里面。原因很简单,用这款软件提供的滤镜处理过的照片看起来更像是个艺术品,而不是那种常见的苍白的脸蛋和色彩失真的风景。这款爆红的程度可媲美Pokémon Go 的App的名字叫做Prisma。对于这个现象级的App,一个笑话是这样说的,“全球有一半的人正在用Pokémon GO抓皮卡丘玩,另一半的人则用Prisma在修图”。

6月24日,俄罗斯总理梅德韦杰夫在自己拥有230万粉丝的Instagram上传了一张莫斯科风景照,解说文字是“滤镜里的莫斯科”。这张照片使用了Prisma的其中一款滤镜,让整张照片呈现出浓郁的铅笔画效果,获得了超过7.8万个点赞。

Prisma 面世是在今年的6月11日,是由一个不足9人的名为Prisma Lab的俄罗斯团队在一个半月以内开发出来。一经面世就引起了轰动,在发布短短4天后就成了年轻人的新宠,10个国家的App Store榜首,两周内下载量超过160万。这个数字也创造了一个App下载的新的记录。

 

包括我在内的许多人喜欢这款应用的原因是因为它所提供的30多款滤镜完全不同于VSCO、Snapseed 以及Instagram 这些成名已久的的软件,经它处理过的图片看起来就像是我们熟悉的毕加索、梵高、爱德华.蒙克的作品,或者是强烈的线条、简洁概括夸张的造型,或者是用色阴暗的紫、绿色调的蚀刻风格,又或者是粗旷而狂野的印象派。总之,一幅色彩暗淡、构图不佳的照片一经处理就变成了大师范的艺术品。这样的软件如何不让像我这样的低级别的摄友痴狂呢。

 

如果仅仅是几个还算不错的滤镜也难逃昙花一现的结局,就如同曾经大热过许多款软件一样。但是如果你可以了解这些滤镜的来源,恐怕你会和我一样会大吃一惊了。不同与传统的设计出来的滤镜,Prisma 有自己一套独特的方法。当用户上传照片时,Prisma系统会使用基于神经网络的人工智能算法,获取著名绘画大师和主要流派的艺术风格,然后对你的照片进行智能化处理。也就是说,它所提供的每一个滤镜,都是模仿过去伟大艺术家的风格而对你的照片进行智能分析和重绘而产生的。

 

作为一个程序员,这个励志故事却有着不一样的味道。通常情况下,我们总是习惯于在我们熟悉的领域去捕捉灵感,并将这种灵光乍现的想法都过程序变成一个个具体的应用。但是如果你已经读过上面的故事,你应该也会与我一样蹦出来这样的一个想法,人工智能或许可以可以让我们跳出这样的思维的局限。让我们可以在跟更广阔的领域去发掘新的需求。

 

为了验证这个想法,我打算复制Prisma 核心算法的实现。当然,这种实现并不是你想像的那么高深莫测。在一篇名为“Neural networks with artistic talent” 的博客文章里面,我大致梳理出来了这个算法的脉络。早在2015年9月在ariV 上面就刊出了德国学者的一篇名为”A Neural Algorithm of Artistic Style“论文。在这片文章里面,了如何使用神经网络学习一副画的绘画风格,并把这种风格用于处理一幅照片使之具有了该种艺术风格。而这篇论文所提出的算法据说所就是Prisma 的核心。

看到这里我想许多人都会产生实现这个算法的冲动。且慢下手,因为有意境找到了一个更简洁的方法。斯坦福大学的Justin Johnson 已经在github 上面为我们提供了一个很好的框架,而基于这个项目的成果实现这个算法就变得轻而易举。

实现这个深度学习的算法需要具备一定的条件,重要的就是计算的性能。而目前解决这个问题的通常思路就是使用GPU来提高处理能力。对于普通程序员来说,我们通常并不具备这一类的资源。但解决问题的思路已经有人给出了。早在2014年2月Netflix 久通过他们的技术博客分享过一篇实现分布式神经网络的文章,文章的题目是“Distributed Neural Networks with GPUs in the AWS Cloud“。

于是我的尝试就从AWS 提供的云计算的环境 。我需要的仅仅是启动一个AWS EC2 的实例,EC2这个服务可以理解为一个托管的虚拟服务器。 Amazon EC2 提供多种经过优化,适用于不同使用案例的实例类型,而适合于深度计算的类型无疑就是G2这个 具有GPU 加速能力的实例。而AWS云计算的好处在这个时候尽显无余,我只需要在控制台上做简单的选择一个完整的计算环境就可以准备就绪。而这一切所需要的时间不过几分钟。

接下来的操作和设置就是照本宣科的过程,

1、安装Torch,这是一个基于Lua语言实现的深度学习的框架。今年年初击败李世石九段的Alpha Go就是基于这个框架

2、安装Load Caffe networks,将Caffe 这个框架加载到 Torch,需要注意的是这个框架依赖于protobuf

3、下载下载Neural Style的实现,这包括了训练好的神经网络模型VGG-19和它的改进型,VGG-19为缺省的设置

最后就是执行你的程序,验证一下你的的成果的时刻了。

也许你会认为Prisma 不过是一个好玩的App,不会真的对这个世界带来多大的影响。但是上面的小小的实验可以证明这一个结论,通过一个好的算法就可以让计算机通过学习以往的经验和积累而迅速成为这个领域的最顶级的专家。今天Prisma 可以让我们称为梵高,明天的一款智能应用也可以让我们我们具有贝多芬、李世石、莫言甚至是爱因斯坦、牛顿一样的能力。我相信这个日期不回很远。

我很喜欢那一本谈论算法的通俗读物《How Algorithms came to rule our world》》。在那本书的最后一段,作者克里斯托弗.斯坦纳有过这样一段描写,我想有这段话做为这篇短文的结尾。

“未来,会编写代码的人有许多事情可以做。如果你还能构思并构造出复杂精妙的算法,那就更好了- 你很有可能通知世界,如果没有机器人抢在你的前头的话。”

作者简介

费良宏

亚马逊AWS首席云计算技术顾问,拥有超过20年在IT行业以及软件开发领域的工作经验。在此之前他曾经任职于Microsoft、Apple等知名企业,任职架构师、技术顾问等职务,参与过多个大型软件项目的设计、开发与项目管理。目前专注于云计算以及互联网等技术领域,致力于帮助中国的开发者构建基于云计算的新一代的互联网应用。

 

 

手把手教你调校AWS PB级数据仓库

什么是一个好的数据仓库?
Redshift是AWS云计算中的一个完全托管的,PB级别规模的数据仓库服务。即使在数据量非常小的时候(比如几百个GB的数据)你就可以开始使用Redshift,Redshift集群可以随着你数据的增加而不断扩容,甚至达到PB级。云计算中数据仓库的优势非常明显,不需要license,不需要预先配置非常大的数据仓库集群,扩容简单,仅仅需要为你实际所使用的数据仓库付费。
Redshift作为一个企业级数据仓库完全支持SQL语法,无学习成本,支持很多种客户端连接,包括各种市场上的BI工具,报表以及数据分析工具。

Redshift的概览
Redshift通过支持大规模并行处理(MPP),列式存储,对不同列数据使用不同数据压缩算法,关系型数据仓库(SQL),灵活的扩容管理等众多优点,兼顾了数仓性能,同时也考虑学习成本及使用成本。

Redshift系统架构及要点
图1,Redshift系统架构图

  • 主节点负责客户端与计算节点之间的所有通讯,编译代码并负责将编译好的代码分发给各个计算节点处理,负责分配数据到不同的计算节点,主节点对客户不可见的,无需客户管理主节点的压力,更重要的是主节点免费。
  • 计算节点是具体的干活的,并处理好的任务送给主节点进行合并后返回给客户端应用程序。每个计算节点都有自己独立的CPU,内存以及直连存储。Redshift集群规模大小通常就是指计算节点的个数以及计算节点机器类型。
  • 节点分片是指将计算节点被分成若干的分片,根据计算节点类型不同,每个节点包含的分片数量不同,通常1个vCPU对应一个分片,ds2的机型除外。每个分片都会分配独立的内存及存储资源,接受来自主节点分配的任务。分片跟另外一个重要概念Dist Key紧密相关, 这里先提一下,接下来会具体介绍Dist Key。
  • 排序键(Sort Key)是一个顺序键,即Redshift会根据这个键来将数据按顺序存储在硬盘上。Redshift的查询优化程序(只要理解有这么个东西存在就好,客户不需要任何维护,对客户也是透明的)也会根据这个排序来进行执行查询优化计划。这是Redshift性能调优的一个非常重要的参数。
  • 分配键(Distribution Key)是控制加载到表的数据如何分布在各个计算节点的一个键,有好几种分布的风格,接下来会重点讲到,这是Redshift调优的非常重要的另外一个参数。

Redshift的几个常用最佳实践
选择最佳排序键

  • 如果最近使用的数据查询频率最高,则指定时间戳列作为排序键的第一列;
  • 如果您经常对某列进行范围筛选或相等性筛选,则指定该列作为排序键;
  • 如果您频繁联接表,则指定联接列作为排序键和分配键;

熟悉Redshift的朋友可能知道可以指定多列作为排序键,而且排序键还有两种方式,组合式和交叉式。限于篇幅的原因,在接下来的调优测试中我们采用的是某一列作为排序键,如果有对其他排序键风格感兴趣的朋友,可以单独联系我们进行讨论。

选择最佳分配键

选择表分配方式的目的是通过在执行查询前将数据放在需要的位置来最大程度地减小重新分配步骤的影响,最好这个查询不需要二次移动数据。

分配键有三种风格,均匀分布(Even),键分布(Key),全分布(All),默认是均匀分布。

  • 根据共同列分配事实数据表和一个维度表;

事实数据表只能有一个分配键。任何通过其他键联接的表都不能与事实数据表并置。根据联接频率和联接行的大小选择一个要并置的维度。将维度表的主键和事实数据表对应的外键指定为 DISTKEY。

  • 根据筛选的数据集的大小选择最大的维度;

只有用于联接的行需要分配,因此需要考虑筛选后的数据集的大小,而不是表的大小。

  • 在筛选结果集中选择基数高的列;

例如,如果您在日期列上分配了一个销售表,您可能获得非常均匀的数据分配,除非您的大多数销售都是季节性的。但是,如果您通常使用范围受限谓词进行筛选以缩小日期期间的范围,则大多数筛选行将位于有限的一组切片上并且查询工作负载将偏斜。

  • 将一些维度表改为使用 ALL 分配;

如果一个维度表不能与事实数据表或其他重要的联接表并置,您可以通过将整个表分配到所有节点来大大提高查询性能。使用 ALL 分配会使存储空间需求成倍增长,并且会增加加载时间和维护操作,所以在选择 ALL 分配前应权衡所有因素。

优化COPY,提高数据加载速度
当你将要数据加载到Redshift的某个表时,不要让单个输入文件过大,最好是将这些输入文件切成多份,具体数量最好是跟分片数量匹配,这样可以充分利用所有分片,配合分配键能达到最佳效果。

图2,COPY输入的最优方式

让COPY选择自动压缩
作为数据仓库,Redshift通常会需要大量导入数据,这时使用做多的,效率最好的是COPY命令。在使用COPY时建议将COMPUPDATE参数设置为ON,这样数据在加载进库时是自动压缩的,好处是可以节省存储空间,提高查询的速度,不过这会增加数据加载进表的时间,这个可以根据你的业务需求,再具体衡量。

Redshift调优实战
测试结论

  1. 选择合适的排序键,分配键,及自动压缩对表的查询速度,存储效率很大提升。本次测试中,优化后查询速度有高达75%的提升,存储空间节省50%。
  2. 相同节点类型情况下,多节点性能比单节点性能提升明显。本次测试中,采用了4节点与单节点对比,4节点查询速度比单节点提升75%。
  3. 节点数量相同的情况下,dc系列节点的查询速度比ds系列节点的查询速度要快。本次测试中,采用了dc1.large和ds1.xlarge两种节点类型进行对比,dc系列节点的查询速度比ds系列快20% 。
  4. 使用JOIN与不使用JOIN查询速度无明显差别。本次测试中,三个不同的查询及对应的JOIN查询,在查询速度上的差别非常小。这部分的详细测试结果,请参见附录一。
  5. 查询速度达到一定值时,再增加节点对查询优化的效果有限。本次测试中,在相同环境中,将节点数量从8个dc1.large节点增加到12个dc1.large节点,三个查询只有一个查询的速度有一定提升,其他2个查询速度基本没有太大变化。这部分的详细测试结果,请参见附录二。

图3,调优前后性能对比图

备注:性能对比图从三个方面进行了对比,数据加载速度表存储空间查询的速度。本次测试的原始数据放在AWS Oregon S3,Redshift也在Oregon区域。

 

测试场景
表1,本次测试中用到的表及表的大小

图4,本次测试中表之间的关系

测试步骤

注意:本次测试步骤已假设Redshift集群已启动,且用户知道如何通过JDBC方式连接Redshift集群。

Before(不做任何优化):

  1. 创建表(不指定排序键和分配键);
  2. 加载数据(不进行自动压缩);
  3. 查询Redshift中各个表的存储空间;
  4. 执行三种不同查询,均取第2次查询所耗时间;
  5. 相同条件,使用JOIN查询所耗时间;

After(指定排序键和分配键,加载数据时进行了自动压缩):

  1. 删除表;
  2. 创建表(指定排序键和分配键);
  3. 加载数据(根据不同数据类型选择合适的压缩算法);
  4. 查询Redshift中各个表的存储空间;
  5. 执行三种不同查询,均取第2次查询所耗时间;
  6. 相同条件,使用JOIN查询所耗时间;

测试截图
图5,单个节点(ds1.xlarge)的数据加载时间(优化前)

图6,单个节点(ds1.xlarge)的数据加载时间(优化后)

图7,单个节点(ds1.xlarge)的数据存储空间(优化前)

图8,单个节点(ds1.xlarge)的数据存储空间(优化后)

图9,单个节点(ds1.xlarge)的查询时间(优化前)

图10,单个节点(ds1.xlarge)的查询时间(优化后)

图11,4个节点(ds1.xlarge)的数据加载时间(优化前)

图12,4个节点(ds1.xlarge)的数据加载时间(优化后)

图13,4个节点(ds1.xlarge)的数据存储空间(优化前)

图14,4个节点(ds1.xlarge)的数据存储空间(优化后)

图15,4个节点(ds1.xlarge)的查询时间 (优化前)

图16,4个节点(ds1.xlarge)的查询时间 (优化后)

图17,4个节点(dc1.large)的数据加载时间 (优化前)

图18,4个节点(dc1.large)的数据加载时间 (优化后)

图19,4个节点(dc1.large)的数据存储空间 (优化前)

图20,4个节点(dc1.large)的数据存储空间 (优化后)

图21,4个节点(dc1.large)的查询时间 (优化前)

图22,4个节点(dc1.large)的查询时间 (优化后)

 

本次测试中用到的命令参数
Before (优化前)

CREATE TABLE part

(

p_partkey     INTEGER NOT NULL,

p_name        VARCHAR(22) NOT NULL,

p_mfgr        VARCHAR(6) NOT NULL,

p_category    VARCHAR(7) NOT NULL,

p_brand1      VARCHAR(9) NOT NULL,

p_color       VARCHAR(11) NOT NULL,

p_type        VARCHAR(25) NOT NULL,

p_size        INTEGER NOT NULL,

p_container   VARCHAR(10) NOT NULL

);

CREATE TABLE supplier

(

s_suppkey   INTEGER NOT NULL,

s_name      VARCHAR(25) NOT NULL,

s_address   VARCHAR(25) NOT NULL,

s_city      VARCHAR(10) NOT NULL,

s_nation    VARCHAR(15) NOT NULL,

s_region    VARCHAR(12) NOT NULL,

s_phone     VARCHAR(15) NOT NULL

);

CREATE TABLE customer

(

c_custkey      INTEGER NOT NULL,

c_name         VARCHAR(25) NOT NULL,

c_address      VARCHAR(25) NOT NULL,

c_city         VARCHAR(10) NOT NULL,

c_nation       VARCHAR(15) NOT NULL,

c_region       VARCHAR(12) NOT NULL,

c_phone        VARCHAR(15) NOT NULL,

c_mktsegment   VARCHAR(10) NOT NULL

);

CREATE TABLE dwdate

(

d_datekey            INTEGER NOT NULL,

d_date               VARCHAR(19) NOT NULL,

d_dayofweek          VARCHAR(10) NOT NULL,

d_month              VARCHAR(10) NOT NULL,

d_year               INTEGER NOT NULL,

d_yearmonthnum       INTEGER NOT NULL,

d_yearmonth          VARCHAR(8) NOT NULL,

d_daynuminweek       INTEGER NOT NULL,

d_daynuminmonth      INTEGER NOT NULL,

d_daynuminyear       INTEGER NOT NULL,

d_monthnuminyear     INTEGER NOT NULL,

d_weeknuminyear      INTEGER NOT NULL,

d_sellingseason      VARCHAR(13) NOT NULL,

d_lastdayinweekfl    VARCHAR(1) NOT NULL,

d_lastdayinmonthfl   VARCHAR(1) NOT NULL,

d_holidayfl          VARCHAR(1) NOT NULL,

d_weekdayfl          VARCHAR(1) NOT NULL

);

CREATE TABLE lineorder

(

lo_orderkey          INTEGER NOT NULL,

lo_linenumber        INTEGER NOT NULL,

lo_custkey           INTEGER NOT NULL,

lo_partkey           INTEGER NOT NULL,

lo_suppkey           INTEGER NOT NULL,

lo_orderdate         INTEGER NOT NULL,

lo_orderpriority     VARCHAR(15) NOT NULL,

lo_shippriority      VARCHAR(1) NOT NULL,

lo_quantity          INTEGER NOT NULL,

lo_extendedprice     INTEGER NOT NULL,

lo_ordertotalprice   INTEGER NOT NULL,

lo_discount          INTEGER NOT NULL,

lo_revenue           INTEGER NOT NULL,

lo_supplycost        INTEGER NOT NULL,

lo_tax               INTEGER NOT NULL,

lo_commitdate        INTEGER NOT NULL,

lo_shipmode          VARCHAR(10) NOT NULL

);

copy customer from ‘s3://lyz/redshift/customer’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key=your-secret-key’

gzip compupdate off region ‘us-west-2’;

 

copy dwdate from ‘s3://lyz/redshift/dwdate’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip compupdate off region ‘us-west-2’;

 

copy lineorder from ‘s3://lyz/redshift/lineorder’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip compupdate off region ‘us-west-2’;

 

copy part from ‘s3://lyz/redshift/part’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip compupdate off region ‘us-west-2’;

 

copy supplier from ‘s3://lyz/redshift/supplier’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip compupdate off region ‘us-west-2’;

 

select count(*) from LINEORDER;

select count(*) from PART;

select count(*) from  CUSTOMER;

select count(*) from  SUPPLIER;

select count(*) from  DWDATE;

 

select stv_tbl_perm.name as table, count(*) as mb

from stv_blocklist, stv_tbl_perm

where stv_blocklist.tbl = stv_tbl_perm.id

and stv_blocklist.slice = stv_tbl_perm.slice

and stv_tbl_perm.name in (‘lineorder’,’part’,’customer’,’dwdate’,’supplier’)

group by stv_tbl_perm.name

order by 1 asc;

 

— Query 1

— Restrictions on only one dimension.

select sum(lo_extendedprice*lo_discount) as revenue

from lineorder, dwdate

where lo_orderdate = d_datekey

and d_year = 1997

and lo_discount between 1 and 3

and lo_quantity < 24;

 

— Query 2

— Restrictions on two dimensions

 

select sum(lo_revenue), d_year, p_brand1

from lineorder, dwdate, part, supplier

where lo_orderdate = d_datekey

and lo_partkey = p_partkey

and lo_suppkey = s_suppkey

and p_category = ‘MFGR#12’

and s_region = ‘AMERICA’

group by d_year, p_brand1

order by d_year, p_brand1;

 

— Query 3

— Drill down in time to just one month

 

select c_city, s_city, d_year, sum(lo_revenue) as revenue

from customer, lineorder, supplier, dwdate

where lo_custkey = c_custkey

and lo_suppkey = s_suppkey

and lo_orderdate = d_datekey

and (c_city=’UNITED KI1′ or

c_city=’UNITED KI5′)

and (s_city=’UNITED KI1′ or

s_city=’UNITED KI5′)

and d_yearmonth = ‘Dec1997’

group by c_city, s_city, d_year

order by d_year asc, revenue desc;

After(优化后):

drop table part cascade;

drop table supplier cascade;

drop table customer cascade;

drop table dwdate cascade;

drop table lineorder cascade;

 

CREATE TABLE part (

p_partkey     integer             not null sortkey distkey,

p_name        varchar(22)      not null,

p_mfgr           varchar(6)      not null,

p_category    varchar(7)      not null,

p_brand1      varchar(9)      not null,

p_color          varchar(11)      not null,

p_type           varchar(25)      not null,

p_size            integer             not null,

p_container   varchar(10)     not null

);

 

CREATE TABLE supplier (

s_suppkey                 integer        not null sortkey,

s_name        varchar(25)    not null,

s_address     varchar(25)    not null,

s_city             varchar(10)    not null,

s_nation         varchar(15)    not null,

s_region        varchar(12)    not null,

s_phone       varchar(15)    not null)

diststyle all;

 

CREATE TABLE customer (

c_custkey     integer        not null sortkey,

c_name        varchar(25)    not null,

c_address     varchar(25)    not null,

c_city             varchar(10)    not null,

c_nation         varchar(15)    not null,

c_region        varchar(12)    not null,

c_phone       varchar(15)    not null,

c_mktsegment      varchar(10)    not null)

diststyle all;

 

CREATE TABLE dwdate (

d_datekey            integer       not null sortkey,

d_date               varchar(19)   not null,

d_dayofweek       varchar(10)   not null,

d_month            varchar(10)   not null,

d_year               integer       not null,

d_yearmonthnum       integer            not null,

d_yearmonth          varchar(8)           not null,

d_daynuminweek       integer       not null,

d_daynuminmonth      integer       not null,

d_daynuminyear       integer       not null,

d_monthnuminyear     integer       not null,

d_weeknuminyear      integer       not null,

d_sellingseason      varchar(13)    not null,

d_lastdayinweekfl    varchar(1)    not null,

d_lastdayinmonthfl   varchar(1)    not null,

d_holidayfl          varchar(1)    not null,

d_weekdayfl          varchar(1)    not null)

diststyle all;

 

CREATE TABLE lineorder (

lo_orderkey                   integer         not null,

lo_linenumber           integer             not null,

lo_custkey                 integer             not null,

lo_partkey                  integer             not null distkey,

lo_suppkey                integer             not null,

lo_orderdate              integer             not null sortkey,

lo_orderpriority          varchar(15)     not null,

lo_shippriority            varchar(1)      not null,

lo_quantity                 integer             not null,

lo_extendedprice       integer             not null,

lo_ordertotalprice      integer             not null,

lo_discount                integer             not null,

lo_revenue                integer             not null,

lo_supplycost            integer             not null,

lo_tax                         integer             not null,

lo_commitdate         integer         not null,

lo_shipmode              varchar(10)     not null

);

 

copy customer from ‘s3://lyz/redshift/customer’

credentials ‘aws_access_key_id=your-key;aws_secret_access_key=your-secret-key’

gzip region ‘us-west-2’;

 

copy dwdate from ‘s3://lyz/redshift/dwdate’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip region ‘us-west-2’;

 

copy lineorder from ‘s3://lyz/redshift/lineorder’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip region ‘us-west-2’;

 

copy part from ‘s3://lyz/redshift/part’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip region ‘us-west-2’;

 

copy supplier from ‘s3://lyz/redshift/supplier’

credentials ‘aws_access_key_id= your-key;aws_secret_access_key= your-secret-key ‘

gzip region ‘us-west-2’;

 

select stv_tbl_perm.name as table, count(*) as mb

from stv_blocklist, stv_tbl_perm

where stv_blocklist.tbl = stv_tbl_perm.id

and stv_blocklist.slice = stv_tbl_perm.slice

and stv_tbl_perm.name in (‘lineorder’,’part’,’customer’,’dwdate’,’supplier’)

group by stv_tbl_perm.name

order by 1 asc;

 

— Query 1

— Restrictions on only one dimension.

select sum(lo_extendedprice*lo_discount) as revenue

from lineorder, dwdate

where lo_orderdate = d_datekey

and d_year = 1997

and lo_discount between 1 and 3

and lo_quantity < 24;

 

— Query 2

— Restrictions on two dimensions

 

select sum(lo_revenue), d_year, p_brand1

from lineorder, dwdate, part, supplier

where lo_orderdate = d_datekey

and lo_partkey = p_partkey

and lo_suppkey = s_suppkey

and p_category = ‘MFGR#12’

and s_region = ‘AMERICA’

group by d_year, p_brand1

order by d_year, p_brand1;

 

— Query 3

— Drill down in time to just one month

 

select c_city, s_city, d_year, sum(lo_revenue) as revenue

from customer, lineorder, supplier, dwdate

where lo_custkey = c_custkey

and lo_suppkey = s_suppkey

and lo_orderdate = d_datekey

and (c_city=’UNITED KI1′ or

c_city=’UNITED KI5′)

and (s_city=’UNITED KI1′ or

s_city=’UNITED KI5′)

and d_yearmonth = ‘Dec1997’

group by c_city, s_city, d_year

order by d_year asc, revenue desc;

附录一
图23,查询1所耗时间,8节点(dc1.large)

图24,查询1使用JOIN所耗时间,8节点(dc1.large)

图25,查询2所耗时间,8节点(dc1.large)

图26,查询2使用JOIN所耗时间,8节点(dc1.large)

图27,查询3所耗时间,8节点(dc1.large)

图28,查询3使用JOIN所耗时间,8节点(dc1.large)

附录二
图29,查询1所耗时间,12节点(dc1.large)

图30,查询2所耗时间,12节点(dc1.large)

图31,查询3所耗时间,12节点(dc1.large)

作者介绍:

郑进佳

亚马逊AWS解决方案架构师,在加入AWS之前,在多家跨国公司有着超过7年的架构设计和项目管理的经验,对AWS云端高可用架构有着深刻的理解,以及对企业级应用如何迁移到云端的架构设计有实战方面的经验。

 

 

 

分布式神经网络框架 CaffeOnSpark在AWS上的部署过程

一、介绍
Caffe 是一个高效的神经网络计算框架,可以充分利用系统的GPU资源进行并行计算,是一个强大的工具,在图像识别、语音识别、行为分类等不同领域都得到了广泛应用。有关Caffe的更多内容请参考项目主页:

http://caffe.berkeleyvision.org/

不过Caffe的常用部署方式是单机的,这就意味着它的水平扩展能力受到了限制。使用者可以通过在系统中添加多个GPU的方式提高并发度,不过其并发能力最终受到单系统可支撑的GPU数量的限制。同时,神经网络计算往往又是计算消耗很大的,所以人们在使用Caffe的时候都可能会希望有一种并行计算框架可以支持Caffe。

而我们知道Spark是基于内存的计算框架,基于Yarn, Mesos或者是Standalone模式,它可以充分利用多实例计算资源。因此,如果能够结合Caffe和Spark,Caffe的能力将得到更充分的发挥。 基于这些原因,Yahoo开源的CaffeOnSpark框架受到的极大的关注。

有关CaffeOnSpark的源代码和相关文档,请大家参考:

https://github.com/yahoo/CaffeOnSpark

今天我们要进一步讨论的是如何在AWS EC2上部署CaffeOnSpark, 充分利用AWS服务提供的GPU实例构建强大的分布式神经网络计算框架。

在CaffeOnSpark的文档中有明确指出EC2上部署CaffeOnSpark的步骤,具体请参考:

https://github.com/yahoo/CaffeOnSpark/wiki/GetStarted_EC2

但是文档的一些部分写得比较简单,初步接触的读者可能在执行过程中遇到一些问题,所以在这里将我个人的安装配置过程整理了一下供大家参考。

安装过程大概可以分为四部分:

下面会在“环境准备”一节中具体描述这几个步骤的细节。

二、环境准备
首先我们打开文档https://github.com/yahoo/CaffeOnSpark/wiki/GetStarted_EC2, 看看文档中刚开始的部分对于环境准备的要求。

里面首先提到我们需要准备“EC2 Key pair”, 就是要准备EC2启动需要的密钥对。当然,为了创建“EC2 Key pair”,为了启动EC2,你首先需要一个AWS账号。有关AWS账号的申请和基本使用这里就不细述了,请参考其它相关文档。需要注意的是你拿到的AWS账号需要有基本的权限才能完成CaffeOnSpark的安装工作,其中包括创建EC2实例,创建安全组等。

“EC2 Key pair”是你在创建EC2实例时创建的密钥对,创建过程中你有一次机会下载私钥文件,就是文中提到的pem文件。如果你之前没有创建过EC2,你也可以直接在EC2控制台的“网络与安全->密钥对”界面中点击“创建密钥对”按钮进行创建。同样,创建过程中你有一次机会下载pem文件,下载后注意保管好该文件,后面都会依赖这个文件。

按文档的描述,有了以上的资源以后就可以执行以下命令:

为了准备环境,我们需要先理解一下上面的脚本。脚本的刚开始部分是一系列变量的定义,我们先了解这些变量的作用。

第一句比较简单,从变量名可以知道这是指定了要使用的AMI的ID:

这个镜像是一个已经安装好Spark、CaffeOnSpark,并加载了常用神经网络测试数据的Ubuntu镜像。该镜像由CaffeOnSpark团队提供,已经共享给所有AWS账号。

不过稍有AWS使用经验的同学会意识到,这样的命令是针对特定的区域(Region)的,因为同一个AMI镜像拷贝到不同AWS区域时它们的AMI ID是不一样的。在命令行中如果指定了一个AMI的ID,就意味着这些命令只能在特定的AWS区域正常工作。

所以我们需要继续查看后续命令,看看哪里指定了区域。幸运的是,命令的第二行就是指定区域的命令:

我们知道区域代码“eu-west-1”指的是欧洲(爱尔兰) 区域,意味着我们运行完这个样例后我们的CaffeOnSpark群集是运行在欧洲(爱尔兰) 区域的。因为EC2 key pair也是按区域分的,所以我们创建的EC2 key pair也应该是在欧洲(爱尔兰) 区域。

为了在欧洲(爱尔兰) 区域创建你的EC2 key pair,你可以点击AWS控制台右上角的区域选择框,选择欧洲(爱尔兰) 区域,然后再按步骤进入EC2的控制台创建EC2 key pair.

同时,你也可以去EC2控制台的“映像->AMI”界面查找镜像ID为ami-5ff7782c的镜像,记得查看时选择“映像类型”为“公有映像”,而不是“我拥有的”。找到这个镜像你还可以仔细查看一下其它相关信息。

如果你发现镜像列表中没有ID为ami-5ff7782c的镜像,有可能你阅读本文的时候相关方已经更新了新的镜像,你可以去CaffeOnSpark的主页

https://github.com/yahoo/CaffeOnSpark 找到更新版本的指导,获得新的镜像ID。

进一步往下看可以看到指定AZ的命令行:

其中eu-west-1c代表c可用区,有关可用区的相关概念在这里也不细述了,对可用区(AZ)概念有疑惑的同学请参考其它AWS基础文档。

再往下是指定worker数量的命令:

表示这个群集需要两个工作实例,需要注意的是创建的Spark群集中有一个主实例,多个工作实例,其中主实例会使用一般的实例类型,而工作实例会使用GPU实例。这里的参数就表示希望启动两个GPU实例作为工作实例。

我们知道,AWS上的GPU实例也有类型之分,现在可以使用的GPU类型有g2.2xlarge和g2.8xlarge两种,到底工作实例会使用什么GPU类型呢,下面的参数就是为了指定工作实例的实例类型。可以选择g2.2xlarge或者是g2.8xlarge。注意下面两行中第二行被注释掉了,表示只有第一句生效,就是使用g2.2xlarge实例类型。

接着是指定竞价实例的最高价格,有关竞价实例的细节请参考AWS相关文档,使用竞价实例大概就是设定一个可以接受的价格去参加实例拍卖的竞价,如果你的出价比别人高你就可以拍得实例。下面这句就是设置竞价为0.8美金每小时:

后面我们继续分析命令会知道,后续执行命令的时候会使用下面的参数,表示希望使用竞价实例作为工作实例。但是这不是CaffeOnSpark所必须的,如果你不希望使用竞价实例,可以不使用以下参数,这样就可以使用“按需实例(OnDemand)”了。

接着就是运行真正的命令了,运行的命令是:

后面带有许多参数,在具体了解不同参数的作用前,我们先得思考,这个spark-ec2命令从哪里来? 如果你之前曾经安装过Spark,在你的Spark主目录里就可以找到ec2目录,里面有spark-ec2命令。 我自己的环境中曾经安装过Spark 1.5,所以也可以找到这个ec2/spark-ec2命令,不过如果我尝试去执行这里的这些命令的话会报错,报“不可知的Spark版本”错误。原因是这里的脚本指定目标Spark是1.6.0, 但是我们运行的Spark是1.5,所以会报错。所以我重新启动了一个新的实例安装了Spark 1.6,以执行spark-ec2命令。有关Spark 1.6的安装,下面的章节会有介绍部分安装过程,现在我们先看看这里spark-ec2命令的不同参数有什么:

其中–key-pair和–identity-file是用来指定EC2 key pair的,就是EC2要使用的密钥,–key-pair用于指定密钥的名称,–identity-file用于指定密钥的pem文件的路径:

接着 –ebs-vol-size是用于指定EBS卷大小的,这里设置了50G,因为镜像大小的原因,你不能把–ebs-vol-size设置的太小,这里就是用样例中的50G吧:

接着是通过–instance-type指定工作实例的实例类型,这里是用了前面定义的变量,按前面我们变量的定义,下面的参数会是用g2.2xlarge作为工作实例的实例类型:

接着是通过–master-instance-type参数指定群集主实例的类型,因为主实例不参与神经网络计算,所以主实例可以不用选择GPU实例,这里是用了m4.xlarge:

然后是用–ami参数指定实例创建时是用的镜像ID:

-s参数用于指定工作实例的数量,这里引用了之前定义的参数,我们把它设置成2,就是会启动两个工作实例:

–spot-price就是前面提到的是用竞价实例的参数,如果你不希望是用竞价实例,直接删除这个参数就好了:

–copy-aws-credentials用于指定是否拷贝AWS授权证书,这里不用细究它的作用:

–hadoop-major-version=yarn 和 –spark-version用于指定Hadoop的运行框架信息和Spark的版本:

–no-ganglia 表示不需要使用ganglia


–user-data用于指定实例启动时的需要完成的动作,这个参数非常重要,CaffeOnSpark安装过程中的很多设置都是因为设置了这个“User-Data”才完成的。这里设置的user-data指向CaffeOnSpark目录的scripts子目录中的。这就意味着我们在使用这个命令的时候应该已经拥有CaffeOnSpark的代码了,具体的获取方法在下一节中有详细描述:

最后就是真正“启动”的参数了,这里同时指定了群集的名称,本例为“CaffeOnSparkDemo”,注意,这个群集名称需要是区域内唯一的,如果你希望进行多次测试,又不希望受之前测试的干扰,每次启动时需要在这里设置不同的群集名称:

理解了整个命令,我们再次总结一下我们需要完成的工作:

我们假设你已经做好了a、b两步,下一节会详细讲解步骤c,紧接着第四节会讲解步骤d:测试校验过程

三、安装过程
下面开始详细讲解我的安装过程。

1.    准备工作机

这一步骤就是启动一个EC2,没有什么特别的需要强调。因为平时都习惯使用Amazon Linux,所以我创建工作机时选择了Amazon Linux镜像。另外就是启动这台EC2也需要一个EC2 key pair,这里可以使用上一节提到的准备好的EC2 key pair, 也可以使用其它EC2 key pair。就是说工作机的密钥和CaffeOnSpark群集的密钥没有关系的,你使用同一对密钥可以,使用不同密钥也可以。

2.    安装Spark 1.6

这一步是在工作机上安装Spark 1.6,如果已经有工作机部署好Spark 1.6的就可以跳过这步。当然,如果你很熟悉Spark的安装过程,也可以直接跳过这一小节。

启动EC2工作机后ssh登录到工作机,按习惯先运行了:, 以更新组件。

接着运行了:来安装git命令,后面获取Spark源代码和CaffeOnSpark源代码都用它呢。

然后通过以下命令获得Spark 1.6的稳定版本:

为了编译Spark,你可以使用Maven或者是sbt,我通过以下命令安装了sbt:

在Spark源代码根目录运行:, 让sbt开始下载需要的组件,运行完进入sbt界面的话先退出来。

接着运行以下命令编译Spark:

我编译的是yarn版本,其实在这里参数-Pyarn不是必须的,我只是后续要执行yarn相关的任务,所以选择了带yarn的版本。

另外,你也可以直接运行Spark源代码目录中的生成可部署的版本,完整命令如下:

为了方便以后使用,我将编译好的Spark移动到以下目录:, 接着,修改一下文件,增加Spark路径的设置:

执行命令让配置项直接生效后,你就可以开始测试一下你的Spark了,比如可以直接运行spark-shell试试。

最后,为了确认spark-ec2命令也正常工作,可以去spark安装目录的ec2子目录下执行命令,不带任何参数,系统会返回spark-ec2命令的帮助文字,你也可以顺便了解以下spark-ec2命令的具体使用方法。

3.    下载CaffeOnSpark源码

如上所述,在安装CaffeOnSpark过程中,其中一个关键点就是在群集实例创建时传入了特定的User-Data,这份User-Data就是来自CaffeOnSpark项目的“scripts/ec2-cloud-config.txt”文件。 为了获得这份“scripts/ec2-cloud-config.txt”文件,我们需要下载CaffeOnSpark源码包,当然你也可以单独下载这个文件,不过为了以后的代码学习,建议整个将CaffeOnSpark项目clone下来。 具体命令如下:

下载了CaffeOnSpark源代码后,修改一下~/.bash_profile文件,设置变量指向CaffeOnSpark的源文件目录,方便后续引用。

4.    运行安装命令

后面就可以开始运行安装命令了,其实这里还有几个参数需要设置,不过为了加深印象,我们可以直接运行命令,遇到错误提示时再修改相关参数。

为了运行安装命令,我们创建一个shell文件,比如叫install.sh,里面的内容就是从CaffeOnSpark项目的EC2安装指导中拷贝的。 为了方便大家,再次贴上该文档的链接:

https://github.com/yahoo/CaffeOnSpark/wiki/GetStarted_EC2

然后再重复把安装命令都贴上来:

给install.sh赋予正确的权限后开始运行它:,然后你会看到下面这样的错误:

错误的意思是没有设置 ,相当于没有设置访问AWS的用户名,该命令无法访问AWS。 所以你需要找到你”Access key”和”secret key”。如果你有足够权限,你可以从AWS控制台上为自己创建“Credential”,然后下载”Access key”和”secret key”。或者你的AWS管理员应该给你分配过”Access key”和”secret key”。有关”Access key”和”secret key”就不在这里展开了,如果你对”Access key”和”secret key”的使用还有疑惑,请参考相关文档。

如果你通过export命令设置了 ,但是没有设置,运行命令时会报另一个错误:

相当于是只设置了用户名,没设置密码。

也就是说你需要将你的设置到环境变量里,才能给CaffeOnSpark安装脚本足够的权限。这里再强调一下,你使用的对应的账号本身需要有足够的权限,比如创建EC2的权限,创建安全组的权限等。

设置完”Access key”和”secret key”后,再次运行,你可能会遇到如下错误:

因为样例命令中并没有设置变量和变量,相当于是没有设置密钥名和密钥pem文件路径。

所以你需要定义 变量和变量,一个指向你的密钥名,一个指向你的密钥pem文件路径。

再次运行命令,应该就可以正常开始安装了,安装过程时间比较长,建议启动screen命令后再执行。

运行命令,其实后台调用的是spark-ec2命令,spark-ec2命令会给AWS发送指令,创建指定的实例并完成相关设置工作。

注意,安装过程中有一步会提示是否格式化硬盘之类的提示,需要手工确认后才能继续进行。

最后,如果一切正常,在一系列的日志输出后,你将看到类似下面这样的提示:

这表明CaffeOnSpark安装成功了,你可以启动一个浏览器,访问以上URL进行验证,打开页面后看到的应该是类似以下页面的Spark主页:

同时,你可以通过ssh连接到Spark的主节点上查看相关文件和日志,具体命令类似于:

要注意,用户名使用的是root

四、校验测试
安装好CaffeOnSpark后,你可以从安装日志里找到Spark群集的主节点DNS名称,接着就可以ssh进去测试了。

如我们平常测试Caffe一样,我们可以使用mnist数据集。CaffeOnSpark的镜像中包括了mnist数据集,在目录CaffeOnSpark主目录的data子目录下。

完整的测试脚本如下,其中关键几部分内容后面有详细解释:

首先我们要留意的是下面这两句:

这两句是针对g2.2xlarge写的,有一个GPU设备,8核。

如果是使用g2.8xlarge的化,需要修改成:

就是有4块GPU,32核。

接着是清除HDFS上测试相关的目录:

然后就是关键部分,通过spark-submit命令向spark群集提交任务,运行的是打包好的Caffe网格计算包中的

有关这里spark-submit命令中的细节就不详细说明了,有机会我们在分析CaffeOnSpark使用方法的文章中继续讨论。

最后,运行完spark-submit命令后,通过HDFS命令输出的结果。

如果执行上面的完整脚本的话,你会在命令行会看到类似下面的输出:

恭喜,你已经顺利完成了CaffeOnSpark的安装测试,可以开始你的分布式深度神经网络之旅了。

五、其它
在安装结束后,你就可以开始使用分布式的神经网络框架了。不过,考虑到实际的情况,还是有几个地方可以再稍微展开一下的。

1. AMI镜像

文档中使用的镜像是在欧洲(爱尔兰)区域的,在CaffeOnSpark公开的文档中没有看到其它区域的镜像。所以,如果你希望在其它区域使用CaffeOnSpark,你需要把镜像拷贝到对应的区域,或者是在对应的区域创建CaffeOnSpark镜像。

如果是拷贝镜像,你没有权限直接拷贝镜像ami-5ff7782c。你需要使用镜像ami-5ff7782c创建一个实例,然后对该实例执行镜像操作,最后才把新创建的镜像拷贝到指定区域。

如果你希望在对应区域创建CaffeOnSpark镜像,可以参考官方文档里有关创建CaffeOnSpark镜像的指引文档:

https://github.com/yahoo/CaffeOnSpark/wiki/Create_AMI

按照这个指引,你可以从一个基础版本的Ubuntu开始,自己从头开始创建CaffeOnSpark镜像。

2.    GPU实例情况

你在搭建自己的CaffeOnSpark框架的时候要提前考察目标区域的是否提供GPU实例。

3.    自动化安装过程

我们这次安装CaffeOnSpark使用了Spark里的spark-ec2命令,spark-ec2命令其实调用了spark-ec.py这个Python脚本,如果你打开脚本spark-ec.py,你可以看到整个spark-ec2命令创建Spark群集的过程。

也就是说,如果你在特殊场景下需要手动安装CaffeOnSpark,或者是在安装过程中有特定的动作要执行,你可以参考spark-ec.py的具体实现。

六、总结
工具最终是为目标服务的,当我们有合适的工具使用时,我们可以更好地思考如何实现目标。本文介绍了CaffeOnSpark的安装过程,希望这篇文章可以帮助你快速设置好CaffeOnSpark这个工具,从而可以更好地实现你的目标。

深度神经网络已经被证明是一个有效的机器学习方法,通过CaffeOnSpark和AWS服务,我们可以建构一个巨大的深度神经网络群集。这时候,海量数据的加载和运算都不再是问题,现在唯一的问题就是你要拿深度神经网络解决做什么。

一切都准备好了,出发吧,祝你好运!

作者:

邓明轩

亚马逊AWS解决方案架构师;拥有15年IT 领域的工作经验,先后在IBM,RIM,Apple 等企业担任工程师、架构师等职位;目前就职于AWS,担任解决方案架构师一职。喜欢编程,喜欢各种编程语言,尤其喜欢Lisp。喜欢新技术,喜欢各种技术 挑战,目前在集中精力学习分布式计算环境下的机器学习算法。

打造DIY版Echo:树莓派+ Alexa 语音服务

关于本文
本文详细阐述了如何在Java客户端和Node.js服务器上使用和测试Alexa语音服务。

本实例需要用Node.Js来获取Login的授权码。

本指导提供详细的操作指南对于在取得示例授权码、依赖性和在运行Pi的过程中相应的硬件部署。对于Windows, Mac,或者通用的Linux指令,可以看这里的向导

开始
所需硬件
1. Raspberry Pi 2 (Model B)在亚马逊上购买升级:当然,Raspberry 3也是可以的,请点击这里查看详细操作。Pi的用户-请点击这里来获取帮助。
2. 微型的-USB 电源线 供树莓派来使用(包括在树莓Pi中)
3. 微型的 SD 卡– 需要预装NOOBS – Raspberry Pi 8GB Preloaded (NOOBS) Micro SD Card
4. 网线
5. USB 2.0 小型麦克风 – Raspberry Pi 没有自带麦克风,需要外接来与Alexa进行交互-在亚马逊上购买
6. 外部扬声器3.5mm音频插座/立体声耳机插孔-在亚马逊上购买
7. 一个 USB 鼠标和键盘,以及一个支持HDMI的外部显示器 – 如果由于某种原因无法通过SSH协议进入到你的树莓派时,我们也推荐你使用USB键盘、鼠标和一个便于使用的HDMI显示器。稍后可以查看关于“SSH”协议的内容。
8. 无线WiFi适配器(可选)在亚马逊上购买
所需技能
1. 基础编程知识
2. 熟悉脚本

0 – 设置树莓派

1. 将装好NOOBS的SD卡插入树莓派

2. 插入USB2.0的迷你麦克风和无线wifi适配器(可选)
3. 插入USB的键盘和鼠标.
4. 连接显示器

1 – 启动树莓派

1. 连接电源                                                                                                                                                                                               2. 你的树莓派会启动,并显示可以安装的操作系统列表                                                                                                                 3. 选择Raspbian 并点击 Install

4. Raspbian将会启动安装流程. 注意:这个过程可能会持续一段时间。                                                                                       5. 按照完毕后,配置菜单 (raspi-config) 将会启动. 这里你可以设置一些基本参数:例如启用摄像头 ,然后选择Finish

6. 启动后,登录到你的树莓派,默认用户名是pi,密码是raspberry

更多信息: raspberrypi.org

2 – 安装应用和依赖包

注意: 你需要安装Terminal来安装使用Alexa Voice Services所需的组件. Terminal 是默认安装的,你可以从桌面上进入Terminal。可以点击这里了解更多关于Terminal的信息。

2.1 – 在树莓派上打开 SSH

SSH允许从另一台计算机远程访问的树莓派的命令行(只要它们都在同一网络上)。这样您不需要将您的树莓派连接到外部显示器 。
SSH默认情况下在树莓派是启用的 。如果你在工作的时候遇到一些有关SSH的问题,确保它已经被安装。在使用raspi-config 组件时,确保SSH已经安装好。
在Terminal中输入以下内容:

sudo raspi-config

然后导航到SSH,单击回车选择安装SSH服务器

2.2 – 使用SSH 登录到Raspberry Pi
现在,请SSH到您的树莓派。这样做之前必须确保你已经知道你的树莓派的IP地址。
键入以下命令到终端:
hostname -I
> 192.168.1.10 //this is an example Raspberry Pi’s IP – it would be different for you

如果你是在Windows PC上,按照这的说明SSH Using windows

现在,你知道你的树莓派的IP地址,就可以使用SSH远程连接到它 。要做到这一点,打开电脑上的终端程序 并键入以下内容:

pi@<YOUR Raspberry Pi IP ADDRESS>

它会提示你输入密码。注:为用户PI默认密码为raspberry
现在你可以远程连接到您的树莓派,并可以且通过SSH远程连接你安装所有的工具。

2.3 – 安装VNC服务器

VNC是一个允许远程控制树莓派界面的图片化的桌面分享系统。这会让操作变得很方便由于摆脱了外部的显示器。

sudo apt-get install tightvncserver

打开VNL服务器

请键入:tightvncserver来打开VNC服务器

你将会被要求设置一个密码。当你需要远程连接到树莓派时,请执行该操作。

在启动时运行VNC服务器

如果你想确保在你重启树莓派之后,VNC服务器会自动启动,请键入以下脚本:

cd /home/pi

cd .config

注意:确保“.”在文件夹前面。这使文件夹成为一个隐藏的文件夹。

mkdir autostart

cd autostart

通过键入以下命令,创建一个新的配置:

nano tightvnc.desktop

编辑文件中的以下文本:

[Desktop Entry]

Type=Application

Name=TightVNC

Exec=vncserver :1

StartupNotify=false

按住ctrl-X后在键入Y来保存更改。

这样的话,下一次你重启VNC服务器时将会自动刷新。

通过VNC服务器连接到树莓派

Mac系统: 请参阅 https://www.raspberrypi.org/documentation/remote-access/vnc/mac.md
Windows系统: https://www.raspberrypi.org/documentation/remote-access/vnc/windows.md
Linux系统: https://www.raspberrypi.org/documentation/remote-access/vnc/linux.md
(如果你喜欢的话)你现在可以不需要连接到显示器,鼠标键盘.随着SSH和VNC的安装, 外部显示器是可选择的。通过树莓派来感受自由吧。

2.4 – 安装VLC
通过输入下面命令获取VLC媒体播放器:
sudo apt-get install vlc-nox vlc-data
注意:如果你正在运行树莓派或者已经安装了VLC,你需要执行以下的命令来移除这俩个相冲突的库文件。
sudo apt-get remove –purge vlc-plugin-notify

sudo rm /usr/lib/vlc/plugins/codec/libsdl_image_plugin.so

无法获取错误:在你安装VLC时,遇到无法获取的错误时,尝试输入以下代码

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install vlc-nox vlc-data

注意:运行”sudo apt-get upgrade”脚本时可能会花费一段时间,请耐心等待
源: https://www.raspberrypi.org/forums/viewtopic.php?f=66&t=67399

确保VLC安装成功

whereis vlc
这条命令会告诉你VLC的安装位置.

大多数的项目会存在/usr/bin中

一般会显示下面的结果
vlc: /usr/bin/vlc /usr/lib/vlc /usr/share/vlc /usr/share/man/man1/vlc.1.gz

设置VLC的环境变量

敲入以下命令:

export LD_LIBRARY_PATH=/usr/lib/vlc
export VLC_PLUGIN_PATH=/usr/lib/vlc/plugins

查看环境变量是否生效

echo $LD_LIBRARY_PATH
> /usr/lib/vlc

echo $VLC_PLUGIN_PATH
> /usr/lib/vlc/plugins

2.5 – 下载并安装 Node.js
确认Node.JS 还没有被安装

node -v
> command not found
然后输入:

sudo apt-get update
sudo apt-get upgrade
设置apt-get  repo的源:

curl -sL https://deb.nodesource.com/setup | sudo bash –
安装Node:

sudo apt-get install nodejs

2.6 – 安装JDK环境
你需要安装 Java Development Kit (JDK) version 8 或者更高.

步骤1: 从Oracle的网站下载 JDK 8.

下载 Linux ARM 32 Soft Float ABI (文件名jdk-8u77-linux-arm32-vfp-hflt.tar.gz)

注意:虽然苹果和一些智能手机有用64位的ARMv8,但是到现在为止没有树莓的64-位ARM的处理器在pis系统上可用

步骤 2: 解压内容 解压缩并安装到 /opt 目录下:

sudo tar zxvf jdk-8u77-linux-arm32-vfp-hflt.tar.gz -C /opt

设置默认Java环境到jdk1.8

sudo update-alternatives –install /usr/bin/javac javac /opt/jdk1.8.0_77/bin/javac 1

sudo update-alternatives –install /usr/bin/java java /opt/jdk1.8.0_77/bin/java 1

sudo update-alternatives –config javac
sudo update-alternatives –config java
注意:如果被要求选择一个可替换,请键入你刚刚安装的相对应的jdk 版本号-例如-jdk1.8.0_77

通过下面的命令确认版本:

java -version
javac -version

2.7 – 安装Maven
步骤 1: 下载 Maven

从以下链接下载apache-maven-3.3.9-bin.tar.gz文件

https://maven.apache.org/download.cgi

步骤 2:解压内容  解压压缩包的内容到 /opt 目录中

sudo tar zxvf apache-maven-3.3.9-bin.tar.gz -C /opt

步骤 3:告诉你的命令解析器在哪里找到maven  由于将在系统配置文件中设置这些,因此这对所有用户可用

创建一个新的文件 /etc/profile.d/maven.sh, 然后在里面输入以下内容:

export M2_HOME=/opt/apache-maven-3.3.9
export PATH=$PATH:$M2_HOME/bin
保存这个文件。退出并退回到树莓派中然后内容脚本就会生效,可以输入以下代码来进行测试:

mvn -version

3 – 开始使用Alexa的语音服务

3.1 – 注册一个免费的亚马逊开发者帐户

获取免费亚马逊开发者账户

3.2 – 下载对树莓派示例应用程序代码和依赖

下载示例应用程序从Github repo上下载zip文件。如果要下载这个包,您必须同意Alexa的语音服务协议

3.3 – 复制并解压你的树莓派.zip文件

1.直接下载你的树莓派的zip文件,复制,然后解压您的树莓派的zip文件。                                                                                   2.请在您的树莓派记下它的位置。进一步的说明将把这个位置<REFERENCE_IMPLEMENTATION>

3.4 – 注册产品并创建一个安全配置文件

1.登录亚马逊开发者门户网站 – developer.amazon.com                                                                                                                   2.单击应用和服务选项卡 – > Alexa的 – > Alexa的语音服务 – >入门

3.在注册产品类型菜单中,选择Device。

4.填写并保存以下值:

设备类型信息

1.设备型号ID:my_device

2.显示名称:My Device

3.单击下一步

安全配置文件

1.单击安全配置文件下拉菜单中选择“创建一个新的配置文件

2.常规选项卡

o安全配置文件名称:Alexa的语音服务示例应用程序安全配置文件

o安全配置文件说明:Alexa的语音服务示例应用程序安全配置文件说明

o单击下一步

客户端ID和客户端密钥会为你生成。

1.现在点击网络设置选项卡

o确保您刚才创建的下拉菜单中选择安全配置文件,然​​后单击“编辑”按钮。

o允许的来源:点击“添加另一个”,然后输入https://本地主机IP:3000

o允许返回网址:点击“添加另一个”,然后输入https://本地主机: 3000 / authresponse

o单击下一步

设备详细信息

1.图像:以下测试图像保存到电脑上,然后上传:

2.分类:其它                                                                                                                                                                                             3.说明:Alexa的语音服务的示例应用程序测试                                                                                                                      4.你对商业化的预期时间是多久?:长4个多月/ TBD您的预计时间表                                                                                        5.有多少设备,你打​​算商业化?:0                                                                                                                                                        6.单击下一步

亚马逊音乐

1.启用亚马逊音乐?:否(您可以选择选择是,如果你想与亚马逊的音乐实验中填写必填字段。但是,亚马逊音乐不要求 使用Alexa的语音服务。)                                                                                                                                                                       2.单击提交按钮

您现在可以生成自签名的证书。

4 – 生成自签名证书

步骤1: 安装 SSL

sudo apt-get install openssl

验证安装

whereis openssl
> openssl: /usr/bin/openssl /usr/share/man/man1/openssl.lssl.gz
修改目录到 <REFERENCE_IMPLEMENTATION>/samples/javaclient.

cd <REFERENCE_IMPLEMENTATION>/samples/javaclient – //your sample apps location

步骤2: 修改SSL配置文件: ssl.cnf, 将所有YOUR_开头的变量值替换为真实值.

Note that countryName must be two characters. If it is not two characters, certificate creation will fail. Here’s what the ssl.cnf file would look like, replacing country, state, locality with your respective info.

请您注意国家名必须是俩个字符。如果不是俩个字符,证书可能会创建失败。单击这里你将会看到ssl.cnf是什么样子的,替换你相对应的国家、洲、地区的信息

步骤3: 将下面文件设置为可执行的属性:

chmod +x generate.sh

步骤4: 运行脚本,生成证书:

./generate.sh

步骤 5: 过程中你会被提示输入一些信息:

1.提示输入product ID, 请输入my_device
2.提示输入 serial number, 请输入123456
3.提示输入password, 请输入你希望的密码 ,例如: talktome (也可以留空)

步骤6: 编辑Node.js server 的配置文件

文件的位置在:

<REFERENCE_IMPLEMENTATION>/samples/companionService/config.js.
进行下列变更:

  • 设置sslKey to                                                                                                                 <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/server/node.key
  • 设置sslCert to                                                                                                              <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/server/node.crt
  • 设置sslCaCert to                                                                                                 <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/ca/ca.crt

注意: 不要使用 ~ 来代表根目录. 必须使用绝对路径.

因此, 不使用~/documents/samples, 而用/home/pi/documents/samples.

步骤7:编辑JAVA客户端的配置文件

这个配置文件在以下位置:

<REFERENCE_IMPLEMENTATION>/samples/javaclient/config.json.

做出以下配置:

  • 设置companionApp.sslKeyStore 到                                                          <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/server/jetty.pkcs12
  • 设置companionApp.sslKeyStorePassphrase 到                                                                                                                      上述第五步中证书生成代码中的密码
  • 设置companionService.sslClientKeyStore 到                                                              <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/client/client.pkcs12
  • 设置companionService.sslClientKeyStorePassphrase到                                                                                                       上述第五步中证书生成代码中的密码
  • 设置companionService.sslCaCert to到                                                       <REFERENCE_IMPLEMENTATION>/samples/javaclient/certs/ca/ca.crt

5 – 安装依赖包
更改目录到 <REFERENCE_IMPLEMENTATION>/samples/companionService

cd <REFERENCE_IMPLEMENTATION>/samples/companionService
输入下列命令来安装依赖包:

npm instal

6 -安装安全配置文件

1.打开web浏览器,并访问以下网址 https://developer.amazon.com/lwa/sp/overview.html

2.在接近页面顶部的位置,在下拉菜单中选择你刚才创建的安全配置然后单击确认

3.进入一个隐私政策地址栏,该地址栏以http:// 或者 https://开头。在此例子中,你可以进入一个模拟仿真的地址栏例如http://example.com
4.[或者]你也可以上传一个图片。这个图片将被显示在使用者的同意登陆亚马逊网站的页面上。
5.单击保存。

6.然后打开Alexa Voice Service示例应用程序安全配置文件,单击显示客户ID和客户隐私。这项操作会展示出客户的ID和客户的隐私。保存这些值。你将会看到这些图片。

7 – 更新配置文件

通过VNC登陆树莓派

步骤1:更新config.js。导航到以下的文件然后在一个文本编辑器中打开该文件。

<REFERENCE_IMPLEMENTATION>/samples/companionService/config.js

在该文件中编辑以下几个值 –

  • 客户ID: 把你在前几个步骤里复制的客户ID以字符串的形式粘贴在客户ID中。
  • 客户秘密: 把你在前几个步骤里复制的客户机密以字符串的形式粘贴在客户机密中。
  • 产品: 产品的对象包含一个你在开发者门户创建的与产品类别ID一致的密钥和一个属于一系列特定的产品标志符的数值。如果你是根据以上的指导操作的,产品序列ID是my device。那个独特的产品序列号可能是任何数字类型的字符串,比如说123456。以JSON产品为例:products:{“my_device”: [“123456”]}

保存文件。

步骤2:更新config.json。 导航到以下的文件然后在一个文本编辑器中打开该文件。

<REFERENCE_IMPLEMENTATION>/samples/javaclient/config.json
在文件中编辑以下值:

  • 产品ID : 以字符串的形式输入my_device
  • dsn: 输入一个数字型的字符串,这个字符串是你在服务器端的config.js页面中用来描述产品对象里的产品标志符数值。例如:123456。
  • provisioning方法: 输入companionService。

保存文件。

步骤 3: 准备pom.xml文件

到下面的文件同时在文本编辑器中打开该文件。

<REFERENCE_IMPLEMENTATION>/samples/javaclient/pom.xml
增加以下的代码在pom.xml文件中< 依赖性 >部分:

<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.1.0</version>
<scope>compile</scope>
</dependency>

8 – 运行服务器

通过VNC登陆树莓派

在你的windows终端或者命令提示符中打出以下代码:

cd <REFERENCE_IMPLEMENTATION>/samples/companionService
npm start

你已经成功开启这个服务器,并且运行在端口3000上。

9 – 开启客户端

打开一个新的Window终端或者选项卡(在Raspbian中 输入SHIFT+CTRL+TAB)

cd <REFERENCE_IMPLEMENTATION>/samples/javaclient

搭建应用平台

在你搭建app平台之前,确认项目是正确的同时所有必要的信息都是有效的。你可以通过执行以下的代码来确认:

mvn validate

通过执行以下代码下载依赖性文件并且搭建应用平台:

mvn install

当安装已经完成时,你在终端上可以看到“成功创建”的信息。

执行客户端的应用:

现在可以输入以下的脚本执行客户端的应用:

mvn exec:exec

10 -从亚马逊登陆页面中获得权限

1.当你执行客户端时,window桌面可能会弹出一个类似于以下文本的讯息:

“通过访问以下网址注册你的设备,然后请跟随指令操作:https://localhost:3000/provision/d340f629bd685deeff28a917一旦完成了请单击OK”

从弹出的窗口中复制URL地址然后粘贴到web浏览器。示例:以下为用来复制和粘贴的URL地址

https://localhost:3000/provision/d340f629bd685deeff28a917

注意:由于使用的是自签名证书,你将看到一个关于不安全网页的警告。这是一个意外。在测试的时候可以忽略这个警告,该网页是安全的。

2.将会跳转到亚马逊的登陆页面。输入你的亚马逊证书。

3.将跳转到开发者权限页面,确认许可你的设备可以通过刚才设置的安全配置文件。

单击Okay

4.你现在将会再次跳转到一个以https://localhost:3000/authresponse开始的后面跟着查询字符串的URL地址栏。这个网页的主体部分将说明设备已经准备好

5.回到Java 应用然后单击OK按钮。这个客户端现在已经可以接受Alexa请求。

6.单击开始音频按钮然后在开始讲话之前等待音频提示。在你听到音频提示前会花费一到二秒

当你完成语音时,单击停止音频的按钮。

现在开始与Alexa 交谈

询问天气:单击开始音频的按钮。

你:今天西雅图的天气怎么样? 单击停止音频按钮。

Alexa:现在西雅图的气温报告。

你可以询问Alexa一些有意思的问题

在单击了“开始音频”之后,一旦你听见了音频提示,下面是一些问题你可以尝试去和Alexa交流

要求歌曲回播: 播放Bruce Springsteen
常识知识:太阳的质量是多少克?
极客:机器人的三定律是什么?
娱乐: 你可以跳rap麽?
设置定时期: 设置一个俩分钟的定时期
设置闹钟: 设置一个早上 7:30 的闹钟

更多的关于音乐回放的功能  “前一首”,“播放/暂停”,和“下一首”按钮在Java客户端交互界面上被用来展示音乐按钮事件。你可以使用音乐按钮而不是与Alexa讲话来改变播放列表的顺序。比如说,你可以按“播放/暂停”按钮来暂停和重复开始一首在音轨上的音乐。

为了更好的展示“播放/暂停”按钮,你可以说出以下命令: 在iHeartRadio上播放DC101,然后按“播放/暂停”按钮。如果按钮按下,这首音乐会终止。再次按下“播放/停止”按钮重新开始这首音乐。

11 – 常见问题解答

我有一个与AVS一起工作的树莓派,但是我不能从Alexa中听到音频回应

检查看一下是否你正在看通过终端的响应和是否在你的Alexa 应用上看到了响应卡。如果是的,你可能需要强制音频通过local 3.5mm插孔,而不是HDMI输出(这可能发生即使你没有一个HDMI显示器插入)。

为了促使音频通过local 3.5mm插孔,打开终端,然后输入以下代码:

sudo raspi-config

可以查看一下链接Raspberry Pi Audio Configuration

如何寻找树莓派的IP地址?

hostname -I

无法获取错误

在你安装VLC时,如果遇到了一些无法获取的错误,试图输入以下代码

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install vlc-nox vlc-data

在npm上有一些问题

如果你在安装结点后遇到一些“npm 无法找到”的问题(树莓的老版本结点), 尝试一些以下操作:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install npm

如果我不能为派找到网络接口怎么办?

可以查看以下URL了解如何在派上建立笔记本电脑的wifi和网络接口的连接https://www.hackster.io/Anwaarullah/sharing-wifi-with-raspberry-pi-using-a-lan-cable-ae1f44

ssl.cnf文件是什么样的?

单击这里将会看到ssl.cnf文件是什么样子的,并且根据你自己的国家、洲、地区来相应的替换

 “无法连接到配套服务”的错误

这可能是因为以下三种情况的其中一种

1.不良证书– 这可能是下面情况的一个结果

  • 不良的ssl cnf文件(可以查看一下常见问题是什么样的)
  • 不正确的java版本
  • 在config.json文件中可能有错误的密码

2.不正确的产品ID -确保以下值是一致的-

  • 设备型号的信息-在亚马逊开发人员门户信息上的设备型号ID
  • 在SSL证书产生的产品ID号(当被generate.sh触发时)
  • 在 config.json上的产品ID
  • 在产品上的“密码”和在config.js页面上 “密码”:[“值”]

3.不正确的DSN- 确保这些值是一致的

  • 在config.json页面上的dsn
  • 在产品上的“值”和在config.js页面上 “密码”:[“值”]

4.配套服务没有运行

作者介绍:

王毅

亚马逊AWS中国云解决方案架构师,获得了AWS解决方案架构师专业级别的认证。专门负责在国内推广AWS云平台技术和各种解决方案。有超过13年的IT领域咨询和实施经验,专注于云计算领域。在此之前,他是IBM全球服务服务部门的资深架构师经理,负责咨询和实施SOA,企业系统集成,云计算平台等解决方案在中国及亚太地区的推广和服务咨询工作。