制造、零售、游戏、公用事业和金融服务业等诸多行业的客户,正面临着近实时地收集、聚合和报告大量数据的重大挑战。在这篇博文中,我们将展示亚马逊云科技的一个无服务器聚合管道。我们先从业务问题的定义开始,接着介绍用于聚合的无服务器架构,然后概述如何最好地利用原生内置到亚马逊云科技云中的安全性与合规性控制。最后,我们将为您提供 Amazon CloudFormation 模板,只需几分钟,您就可以在自己的帐户中完成该管道的设置。
我们将通过银行业的一个具体案例,介绍技术层面上面临的挑战:交易风险聚合。一般而言,金融机构会为其员工在交易大厅中执行的每笔交易,指定一种或多种风险类型(例如,货币风险或利率风险)以及相应的风险值。银行的风险管理部门需要根据特定属性(如地理区域或风险类型),确保始终一致地观察所有交易聚合后的总风险值。
从传统意义上来讲,风险报告依据的是经过隔夜计算得出的结果(而且现在也仍是整个过程中的重要部分),这意味着交易员需要根据过期的数据做决定。监管机构日益要求公司更为全面、及时地了解客户的头寸。巴塞尔银行监管委员会 (BCBS) 概述了有关数据聚合和风险报告及时性的具体原则。对这些风险进行近实时的监测,可让金融机构无论是在正常情况下,还是在面对压力的情况下,都能更为迅速地做出反应。
分层数据聚合
想象一个由诸多记录组成的数据流,每个记录代表一个交易操作。为简单起见,我们假设,每笔交易恰好对应一种风险类型和相应的风险值。因此,每次交易操作都会改变(增加或减少)银行面临的总体风险。任何一家银行都要始终一致地实时观察其总风险敞口,这一点变得至关重要。
数据表示
我们只触及了解风险聚合所需的行业特定元素的核心方面,同时还要关注各种行业和工作负载之间共同面临的技术挑战和权衡取舍。每个风险记录都由一个 JSON 对象表示,该对象包含以下属性:
TradeID,与风险相关的唯一交易标识符。因为我们假设交易和风险之间是一对一的关系,所以这个 ID 也唯一地描述了风险报文。
风险值。
Unix Timestamp,与执行交易的时刻相对应。
一组分层属性,它们将每个风险与银行总体风险敞口中的特定类别关联起来。
以下代码显示了一个风险报文示例:
"RiskMessage": {
TradeID : '0d957268-2913-4dbb-b359-5ec5ff732cac',
Value : 34624.51,
Timestamp : 1616413258.8997078,
Hierarchy : {'RiskType': 'Delta', 'Region': 'AMER', 'TradeDesk': 'FXSpot'}
}
*左滑查看更多
当我们考虑引入的样本记录结构时,银行的聚合视图可能如下所示。
风险 | 总和 |
Delta | 13573731.17 |
即期外汇交易 | 1564334.26 |
EMEA | 1479918.76 |
APAC | 1551.38 |
AMER | 82864.11 |
外汇期权交易 | 12009396.91 |
EMEA | 3802412.64 |
APAC | 3343995.48 |
AMER | 4862988.79 |
PV | 13523427.04 |
即期外汇交易 | 690762.45 |
EMEA | 491582.25 |
APAC | 1239.76 |
AMER | 197940.45 |
外汇期权交易 | 12832664.59 |
EMEA | 3665092.03 |
APAC | 4037012.23 |
AMER | 5130560.33 |
总计 | 27097158.21 |
尽管已从隔夜计算转变为近实时的处理,但重要的是系统在处理数据时能够不发生丢失或重复,特别是对于金融服务行业而言,任何报文丢失或重复都可能产生重大的经济影响。
因此,我们按照以下原则设计了管道的最终迭代:
只执行一次报文处理 – 无论管道的任何组件出现故障,都不会出现报文丢失或重复。
扩展、高吞吐量和低延迟 – 能够根据负载动态扩展,对间歇性负载峰值作出反应,每秒处理数万个更新,同时保持近实时的延迟。
云原生无服务器架构 – 充分利用托管的亚马逊云科技服务优化弹性和性能,同时最大程度减少运营管理。
基础设施即代码 (IaC) – 使用 Amazon CloudFormation 模板,我们可以部署一个数据聚合管道的实例,并可在几分钟内按需扩展该实例。
架构概述:无状态、至少一次的聚合管道
我们可水平扩展的高效数据聚合管道所用的架构,基于以下三个亚马逊云科技服务:Amazon Kinesis、Amazon Lambda 和 Amazon DynamoDB。
Amazon Kinesis 是一个完全托管的解决方案,它可以轻松、实时地摄取、缓冲和处理流数据。Amazon Kinesis 可以处理任意数量的流数据,并且可以极低的延迟处理来自数十万个数据源的数据。在我们的架构中,使用 Amazon Kinesis Data Streams 作为数据进入亚马逊云科技云的入口点。
Amazon Lambda 是一个无服务器计算服务,让您无需预置或管理服务器即可运行代码。Amazon Lambda 通过响应特定触发器运行代码,从而自动扩展您的应用程序。我们管道的聚合逻辑封装在两个不同的 Amazon Lambda 函数中,由不同的数据流自动调用。
最后,Amazon DynamoDB 是一个完全托管的多区域持久 NoSQL 数据库,具有内置安全性、备份和还原功能,无论规模大小,均可提供个位数的毫秒级性能。管道的持久层由多个 Amazon DynamoDB 表组成。
下图所示架构采用映射和归约方法,多个并行的映射 Amazon Lambda 函数对数据进行预聚合,并将其归约到可管理的体量,以便单个归约 Amazon Lambda 函数对数据一致地进行聚合。
总体来看,数据从上游数据源穿过管道后进入 Amazon DynamoDB 聚合表,并准备好供前端使用。在这篇博文中,我们使用了一个样本记录生成器,它扮演上游数据源的角色(我们称其为发生器)。一般来说,您可能会有一个或多个数据源在本地、亚马逊云科技或第三方进行托管。为简单起见,我们的 Amazon CloudFormation 模板只提供一个在亚马逊云科技云中托管的数据源。发生器产生随机报文,然后将报文摄取到 Amazon Kinesis 数据流中。将此数据流定义为一系列 Amazon Lambda 函数(我们称其为映射 Amazon Lambda 函数)的事件源。
每次调用时,映射 Amazon Lambda 函数从数据流提取一批报文(最多 5,000 条),计算其中所有报文的聚合值(基于配置的聚合层次结构),然后将预聚合的数据写入 Amazon DynamoDB 归约表。
该表的分区键称为 MessageHash,用于确保我们不会意外地对任何一批报文进行多次处理。为此,映射 Amazon Lambda 函数不仅计算聚合值,而且还计算 Amazon Lambda 调用事件中完整记录列表的 SHA256 哈希值。正是 MessageHash 对每批报文进行了唯一标识。当将聚合结果持久保存到归约表时,我们对包含这一批聚合值的单一项执行有条件写入。仅当分区键的值(我们描述的哈希值)在之前未曾出现过,才会运行写入操作。这可以确保我们不会重复处理相同的批次。然而,在这种架构下,在此管道的第一阶段仍有很小的几率会出现个别报文的重复,比如当 Amazon Kinesis 数据流摄取报文后,发生器再次尝试生成该报文。
此外,归约表会启用 Amazon DynamoDB Streams:Amazon DynamoDB 流是一种有关 Amazon DynamoDB 表中的项目更改的有序信息流。当您对表启用流时,Amazon DynamoDB 会捕获项目级的所有数据修改信息,并将这些更新发送到可进一步处理的流中。
归约表流被定义为归约 Amazon Lambda 函数的事件源。此 Amazon Lambda 函数由写入归约表的一批项目所调用(写入归约表中的每一项,都是之前由映射函数计算的多达 5,000 条风险报文的经过归约的预聚合)。对在这一环境中需要支付的成本予以简单的说明:启用 Amazon DynamoDB Streams 免费,但是使用 Amazon SDK 从流中读取数据时将产生费用。然而,当您将 Amazon DynamoDB Streams 连接到 Amazon Lambda 函数后,从 Amazon DynamoDB Streams 读取则无需任何费用,就像我们对这个特定架构所进行的操作。
归约函数执行如下操作:
计算调用此函数的预聚合项目批次的全部聚合值。
使用单个事务性写入操作更新聚合表中的值,该操作用上一步的结果增加所有当前值。
归约 Amazon Lambda 函数配置的预留并发数为 1,因此任何时候均仅允许运行此函数的单个实例。这可以防止当多个函数试图更新聚合表中的相同行时,发生竞争情况和写入冲突。
最后,我们还编写了一个基于 Python 的简单前端,它会定期轮询聚合的数据表是否有更新,并在命令行 shell 中显示结果。
吞吐量和容量要求
发生器按照我们描述的模式,以每秒多达 50,000 条报文的速度生成随机报文,并将它们摄取到聚合管道中。
此架构在维护水平可扩展性的同时确保了一致性:如果数据流观察到高吞吐量,管道会自动调用映射 Amazon Lambda 函数的大量实例。让我们假设平均有 100 个映射 Amazon Lambda 函数并发运行,每个函数预聚合 500 条风险报文,运行时为 1,000 毫秒,然后将结果写入归约表。归约函数的单个活动实例只需每秒计算该表中 100 个新项的总和,然后增加总聚合值。在此例中,管道处理的总吞吐量为每秒 50,000 条报文,资源要求相当低,大约每秒 100 次并发函数调用和 100 次 Amazon DynamoDB 写入操作。
评估
下图显示了一项测试的结果,在这项测试中,我们在大约 200 秒内摄取了 1,000 万条报文(总吞吐量按 20 秒内的滚动平均值计算)。横轴表示时间,纵轴则在以下每张图的顶部注明。
总吞吐量相当稳定,为每秒 50,000 条报文,在测试的大部分情况下,端到端平均延迟保持在 3-4 秒(一个峰值大约为 10 秒),如以下指标所示。
在美国东部(俄亥俄州)地区,使用此架构以每秒 50,000 条报文、每天 24 小时、每周 7 天的规模运行管道,每月费用不到 3,000 美元。
上面的图形是使用 Grafana 结合 InfluxDB 所生成的。我们的源代码在 Common/constants.py 文件中包含一个标志,可以将其设置为 true,以便开始将数据发送到 InfluxDB,从而使用 Grafana 启用性能可视化。如果您希望如此,还需要用 InfluxDB 设置一个 Grafana 实例,例如使用 Amazon Managed Service for Grafana,然后在 Common/constants.py 文件中提供该实例的 IP,以及 InfluxDB 的连接字符串。
安全性
在亚马逊云科技,安全性是我们的首要任务。这篇博文中概述的架构继承了原生内置到亚马逊云科技云中并且与 Amazon Kinesis、Amazon Lambda 和 Amazon DynamoDB 集成在一起的安全性和合规性控制。
通常,在考虑此类数据聚合管道的潜在威胁时,首先会想到机密性、数据完整性和可用性。在这一部分,我们将讨论如何使用不同的亚马逊云科技服务来消除这些问题。
机密性
我们希望确保只有授权方可以访问管道中的数据。因此,我们使用了 Amazon Identity and Access Management ( Amazon IAM ) 策略提供的粒度访问控制。我们架构中的每个 Amazon Lambda 函数,都只被授权从前一个流组件中读取以及写入到下一个流组件。为了通过一个具体的案例进行介绍,让我们看一段 Amazon CloudFormation 模板中附加到映射 Amazon Lambda 函数的 Amazon IAM 策略摘录:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadFromKinesisStream",
"Effect": "Allow",
"Action": [
"kinesis:DescribeStream",
"kinesis:GetRecords",
"kinesis:GetShardIterator",
"kinesis:ListStreams",
"kinesis:ListShards"
],
"Resource": "arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${KinesisStream}"
},
…
{
"Sid": "WriteToDynamoDBTable",
"Effect": "Allow",
"Action": "dynamodb:PutItem",
"Resource": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ReduceTable}"
}
]
}
*左滑查看更多
该 Amazon Lambda 函数只被授权执行管道中的数据流所需的特定 API 调用。
在这篇博文中提供的 Amazon CloudFormation 模板中,上游数据源和前端在一个 Amazon Cloud9 实例中运行。Amazon Cloud9 是一个基于云的集成开发环境 (IDE),它让您只用浏览器即可编写、运行和调试代码。它包括一个代码编辑器、调试器和终端。
此实例使用默认的亚马逊云科技托管的临时凭证方法。当此实例调用任何亚马逊云科技服务时,Amazon Cloud9 将检查发出调用的亚马逊云科技实体(例如 Amazon IAM 用户)是否具有执行请求操作的必要权限。如果没有权限或被明确拒绝,则请求失败。因此,授权基于访问 Amazon Cloud9 实例的 Amazon IAM 用户的权限。为了让管道按预期工作,您需要确保 Amazon IAM 用户具有必要的特权。
首先,他们需要运行上游数据源所需的最低权限:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "WriteToKinesisStream",
"Effect": "Allow",
"Action": "kinesis:PutRecords",
"Resource": "arn:aws:kinesis:<AWS_REGION>:<AWS_ACCOUNT_ID>:stream/<KINESIS_STREAM_NAME>"
}
]
}
*左滑查看更多
他们还需要运行前端的最低权限:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadFromDynamoDBTable",
"Effect": "Allow",
"Action": "dynamodb:Scan",
"Resource": "arn:aws:dynamodb:<AWS_REGION>:<AWS_ACCOUNT_ID>:table/<AGGREGATE_TABLE_NAME>"
}
]
}
*左滑查看更多
在这两种情况下,都需要将占位符 < AWS_REGION >、< AWS_ACCOUNT_ID > 和 < KINESIS_STREAM_NAME > 或 < AGGREGATE_TABLE_NAME > 替换为其各自的值。
此外,我们自然希望确保数据端到端加密。管道所有阶段之间的通信使用安全的 HTTPS 协议,并且 Amazon DynamoDB 和 Amazon Kinesis 都允许由 Amazon Key Management Service (Amazon KMS) 管理加密密钥的静态加密(也称为服务器端加密)。
Amazon Kinesis Data Streams 默认没有服务器端加密,所以我们在提供的 Amazon CloudFormation 模板中启用了它。相反,所有存储在 Amazon DynamoDB 中的用户数据默认为完全静态加密。您可以选择不同类型的 Amazon KMS 密钥;在这篇博文中,我们为 Amazon Kinesis 和 Amazon DynamoDB 使用亚马逊云科技拥有的密钥。
数据完整性
我们介绍的最低特权最佳实践也有助于确保数据完整性。写入权限严格限制管道的必要组件。此外,数据完整性依赖于管道是否能够一致地处理数据,即能够防止报文重复和丢弃。您可以在通过只执行一次处理,构建容错、无服务器的数据聚合管道中找到有关只执行一次处理语义的更多详细信息。
可用性
最后,对那些受到高度监管的行业客户来说,比如我们所提到的银行业,一个特别重要的问题是可用性。与业务相关的任何系统停止运行,都可能会带来高昂的经济损失,因此我们选择使用具有内置容错能力和可用性的完全托管、无服务器的亚马逊云科技服务(即 Amazon Kinesis、Amazon Lambda 和 Amazon DynamoDB)。
在您的亚马逊云科技帐户探索此管道
您可以使用提供的 Amazon CloudFormation 模板,在自己的亚马逊云科技帐户中轻松部署这篇博文中描述的架构。您可借助此模板部署的管道,测试并探索无服务器数据聚合。它附带一个 Amazon Cloud9 实例,可用于运行发生器以及前端。
在您的帐户中运行所提供的 Amazon CloudFormation 模板可能会产生费用。不管您选择哪个地区,如果完全按照这篇博文中描述的步骤进行操作,产生的费用不会超过 1 美元,但使用后务必要清理所有资源。
如要部署该解决方案架构,请完成以下步骤:
下载下方的 Amazon CloudFormation 模板。
在您所选的地区,进入 Amazon CloudFormation 控制台。
选择 Create stack(创建堆栈),然后选择 With new resources (standard)(使用新资源(标准))。
选择 Upload a template file(上传模板文件)并选择您下载的文件。
选择 Next(下一步)。
为堆栈输入一个名称,例如 ServerlessAggregationStack。
选择 Next(下一步)。
其他所有选项保留默认值,然后选择 Next(下一步)。
选择 I acknowledge that Amazon CloudFormation…(我确认该 Amazon CloudFormation...),然后选择 Create stack(创建堆栈)。 创建堆栈需要 1–2 分钟。完成后,运行管道就准备就绪了。
10. 在 Amazon Cloud9 控制台上,找到实例 StatelessDataProducer。如未找到,则要确定您所在的地区与创建 Amazon CloudFormation 堆栈时所选的地相同。
11. 选择 Open IDE(打开 IDE)。
12. 打开一个终端并运行以下命令,使管道准备就绪:
cd ~/environment/ServerlessAggregation
chmod +x prepare_stateless.sh
./prepare_stateless.sh
*左滑查看更多
管道已准备就绪!
13. 使用以下代码启动前端:
cd ~/environment/ServerlessAggregation/Frontend
python3 frontend.py
*左滑查看更多
14. 打开另一个终端,启动发生器:
cd ~/environment/ServerlessAggregation/Producer
python3 producer.py
*左滑查看更多
数据应该开始分批到达前端。您可比较两组数据来确认聚合的精确性。在少数情况下,您可能会观察到由于管道中的重试而引入重复,如前所述。
再次运行发生器前,您可能要运行以下命令,重置在前端显示的聚合表:
cd ~/environment/ServerlessAggregation/Producer
python3 clearTables.py
*左滑查看更多
清理
清理您使用的资源,以免产生不必要的费用:
在 Amazon CloudFormation 控制台选择 Stacks(堆栈)。
选择您创建的堆栈 (ServerlessAggregationStack)。
选择 Delete(删除)。
选择 Delete stack(删除堆栈)进行确认。
您应该看到状态 DELETE_IN_PROGRESS,而且在 1–2 分钟后,应该会完成删除,该堆栈从列表中消失。
结语
在这篇博文中,我们介绍了一个基于Amazon Kinesis Data Streams、Amazon Lambda 和 Amazon DynamoDB 的近乎实时进行数据聚合的无服务器架构。这解决了制造、零售、游戏、公用事业和金融服务等各行业的客户面临的业务问题。基于来自银行业的一个具体示例,我们演示了管道可以通过水平扩展来实现每秒处理多达 50,000 条报文。
尽管该解决方案显示出了卓越的可扩展性、低延迟和成本效率,但仍然有两个限制需要我们进一步改进:
管道无状态 – 不会保留单笔交易的值,这使得无法修改在较早阶段进行的交易。
管道并没有设计为提供只执行一次处理语义 – 尽管我们可以确保对 Amazon DynamoDB 的任何写入均为幂等性(后面会提供更多详细信息),但无法识别 Amazon Kinesis 数据流中的重复报文(例如由于发生器重试),因为我们不跟踪已经处理的报文的 ID。
本篇作者
Lucas Rettenmeier
亚马逊云科技解决方案架构师
在亚马逊云科技的旅程始于业务发展部。在完成海德堡大学 ( Heidelberg University ) 物理学硕士学位之后,于2020年作为解决方案架构师重新加入了亚马逊云科技。
Kirill Bogdanov
亚马逊云科技高级解决方案架构师
专注于研究 cloud-native 架构设计和原型实现,去构建可靠、可伸缩、安全且经济高效的解决方案,确保客户的长期业务目标和战略得到实现。
扫描上方二维码即刻注册
听说,点完下面4个按钮
就不会碰到bug了!