用Redis和AWS Lambda建立一个Twitter排行榜应用程序的教程

286 阅读5分钟

用Redis和AWS Lambda建立一个Twitter排行榜应用程序

使用一个实际应用来演示如何将Redis与AWS Lambda集成。

这是一个由两部分组成的系列的第二篇博文,使用一个实际应用来演示如何将Redis与AWS Lambda集成。第一篇文章是关于解决方案的概述和部署。希望你能从头到尾试一遍。按照承诺,第二部分将涵盖基础设施方面(具体来说是IaC),它由三个(CDK)堆栈组成(在一个CDK应用程序的背景下)。

由于CDK Go的支持,我将提供一个用Go编写的CDK代码的演练。AWS云开发工具包(CDK)是关于IaC(基础设施即代码)的。

解决方案的架构

作为复习,这里是解决方案的高层架构。

架构的划分

该架构分为两个逻辑部分:

  1. 第一部分处理推文的摄取。一个Lambda函数获取推文(来自Twitter),为每条推文提取标签,并将其存储在MemoryDB中(在Redis排序的集合中)。这个函数根据CloudWatch触发器中的规则,按照时间表被调用。
  2. 第二部分提供了排行榜的功能。这是另一个Lambda函数,它提供了一个HTTP(s)端点(感谢Lambda函数的URL)来查询排序集并提取前10个标签(排行榜)。

服务概述

以下是解决方案中涉及的服务的快速概述:

  • Amazon MemoryDB for Redis:它是一个持久的、与Redis兼容的内存数据库服务,从而使您能够使用与现在已经使用的Redis数据结构、API和命令一样的灵活友好的Redis构建应用程序。
  • Lambda Function URL是一个相对较新的功能(在写这篇博客的时候),它为你的Lambda函数提供了一个专门的HTTP(S)端点。当你只需要为你的函数提供一个单一的端点(例如,作为一个webhook),而不想设置和配置API网关时,它真的很有用。
  • 如前所述,AWS云开发工具包(CDK)是关于IaC(基础设施即代码)的。它是一个在代码中定义云基础设施并通过AWS CloudFormation进行配置的框架。你可以从支持的编程语言列表中选择(在撰写本文时,该列表包括TypeScript、JavaScript、Python、Java、C#/.Net和Go,处于开发者预览阶段),将你的基础设施组件定义为代码,就像你对任何其他应用程序一样

CDK代码走过

该解决方案由三个(CDK)堆栈组成(在单个CDK应用的背景下):

  • 第一个堆栈部署了一个VPC(还有子网,NAT网关等),一个用于Redis集群的MemoryDB,以及一些安全组。
  • 第二个堆栈部署了第一个Lambda函数,负责将推文数据导入Redis。
  • 最后,第三个堆栈部署了排行榜Lambda函数。

请注意,由于这是一个演示应用程序,我们可以将上述所有内容作为一个单一的栈的一部分。然而,我想证明你如何在一个CDK应用程序中利用多个堆栈。对于一个更复杂的生产基础设施来说,使用多个堆栈是有意义的,这样可以保持你的资源解耦,并使你的CDK逻辑更容易处理和推理。

让我们来看看代码,一次一个栈。

请注意,为了简洁起见,一些代码已经被编辑/删减了 - 你可以随时参考GitHub repo中的完整代码。

1. 从基础设施堆栈开始:

stack := awscdk.NewStack(scope, &id, &sprops)
vpc = awsec2.NewVpc(stack, jsii.String("demo-vpc"), nil)

authInfo := map[string]interface{}{"Type": "password", "Passwords": []string{memorydbPassword}}
user = awsmemorydb.NewCfnUser(stack, jsii.String("demo-memorydb-user"), &awsmemorydb.CfnUserProps{UserName: jsii.String("demo-user"), AccessString: jsii.String(accessString), AuthenticationMode: authInfo})
acl := awsmemorydb.NewCfnACL(stack, jsii.String("demo-memorydb-acl"), &awsmemorydb.CfnACLProps{AclName: jsii.String("demo-memorydb-acl"), UserNames: &[]*string{user.UserName()}})

//snip .....

subnetGroup := awsmemorydb.NewCfnSubnetGroup(stack, jsii.String("demo-memorydb-subnetgroup"), &awsmemorydb.CfnSubnetGroupProps{SubnetGroupName: jsii.String("demo-memorydb-subnetgroup"), SubnetIds: &subnetIDsForSubnetGroup})

memorydbSecurityGroup = awsec2.NewSecurityGroup(stack, jsii.String("memorydb-demo-sg"), &awsec2.SecurityGroupProps{Vpc: vpc, SecurityGroupName: jsii.String("memorydb-demo-sg"), AllowAllOutbound: jsii.Bool(true)})

memorydbCluster = awsmemorydb.NewCfnCluster(//... details omitted)

//...snip

twitterIngestFunctionSecurityGroup = awsec2.NewSecurityGroup(//... details omitted)
twitterLeaderboardFunctionSecurityGroup = awsec2.NewSecurityGroup(//... details omitted)

memorydbSecurityGroup.AddIngressRule(//... details omitted)
memorydbSecurityGroup.AddIngressRule(//... details omitted)

总结一下:

  • awsec2.NewVpc: 一行代码就可以创建VPC和相关组件,如公共和私人子网、NAT网关等。与标准的CloudFormation模板相比,你需要写出这样的模板来完成这项工作
  • 我们为MemoryDB集群创建了一个用户(以及用于验证的密码)、ACL(用于授权的访问控制列表)和子网组。在用awsmemorydb.NewCfnCluster 创建集群时,请参考它们。
  • 我们还创建了所需的安全组。他们的主要作用是允许Lambda函数访问MemoryDB。我们指定了明确的入站规则,使之成为可能。
    • 一个用于MemoryDB集群
    • 两个Lambda函数各有一个

注意: 我们正在为Redis使用MemoryDB的L1构造。

2. 下一个堆栈部署了tweets的摄入Lambda函数。

//....

memoryDBEndpointURL := fmt.Sprintf("%s:%s", *memorydbCluster.AttrClusterEndpointAddress(), strconv.Itoa(int(*memorydbCluster.Port())))

lambdaEnvVars := &map[string]*string{"MEMORYDB_ENDPOINT": jsii.String(memoryDBEndpointURL), "MEMORYDB_USER": user.UserName(), "MEMORYDB_PASSWORD": jsii.String(getMemorydbPassword()), "TWITTER_API_KEY": jsii.String(getTwitterAPIKey()), "TWITTER_API_SECRET": jsii.String(getTwitterAPISecret()), "TWITTER_ACCESS_TOKEN": jsii.String(getTwitterAccessToken()), "TWITTER_ACCESS_TOKEN_SECRET": jsii.String(getTwitterAccessTokenSecret())}

awslambda.NewDockerImageFunction(stack, jsii.String("lambda-memorydb-func"), &awslambda.DockerImageFunctionProps{FunctionName: jsii.String(tweetIngestionFunctionName), Environment: lambdaEnvVars, Timeout: awscdk.Duration_Seconds(jsii.Number(20)), Code: awslambda.DockerImageCode_FromImageAsset(jsii.String(tweetIngestionFunctionPath), nil), Vpc: vpc, VpcSubnets: &awsec2.SubnetSelection{Subnets: vpc.PrivateSubnets()}, SecurityGroups: &[]awsec2.ISecurityGroup{twitterIngestFunctionSecurityGroup}})

//....

与之前的堆栈相比,它相当简单。我们首先定义Lambda函数所需的环境变量(包括Twitter API凭证),然后将其部署为Docker镜像

对于要打包成Docker镜像的函数,我使用了Go:1.x基础镜像,但你也可以探索其他选项。在部署过程中,Docker镜像在本地构建,推送到一个私有的ECR注册中心,最后创建Lambda函数:所有这些,只需要几行代码就可以了

值得检查的是L2结构(在撰写本文时处于alpha状态),它使使用CDK部署Go函数变得更加简单。你可以参考该文档

注意,MemoryDB集群和安全组是自动从以前的堆栈中引用/查找的(不是重新创建的!)。

3. 最后,第三个栈负责处理排行榜的Lambda函数。它与前一个非常相似,只是增加了Lambda函数的URL(awslambda.NewFunctionUrl ),我们用它作为栈的输出。

//....
memoryDBEndpointURL := fmt.Sprintf("%s:%s", *memorydbCluster.AttrClusterEndpointAddress(), strconv.Itoa(int(*memorydbCluster.Port())))

lambdaEnvVars := &map[string]*string{"MEMORYDB_ENDPOINT": jsii.String(memoryDBEndpointURL), "MEMORYDB_USERNAME": user.UserName(), "MEMORYDB_PASSWORD": jsii.String(getMemorydbPassword())}

function := awslambda.NewDockerImageFunction(stack, jsii.String("twitter-hashtag-leaderboard"), &awslambda.DockerImageFunctionProps{FunctionName: jsii.String(hashtagLeaderboardFunctionName), Environment: lambdaEnvVars, Code: awslambda.DockerImageCode_FromImageAsset(jsii.String(hashtagLeaderboardFunctionPath), nil), Timeout: awscdk.Duration_Seconds(jsii.Number(5)), Vpc: vpc, VpcSubnets: &awsec2.SubnetSelection{Subnets: vpc.PrivateSubnets()}, SecurityGroups: &[]awsec2.ISecurityGroup{twitterLeaderboardFunctionSecurityGroup}})

funcURL := awslambda.NewFunctionUrl(stack, jsii.String("func-url"), &awslambda.FunctionUrlProps{AuthType: awslambda.FunctionUrlAuthType_NONE, Function: function})

awscdk.NewCfnOutput(stack, jsii.String("Function URL"), &awscdk.CfnOutputProps{Value: funcURL.Url()})

这篇博文的内容就到此为止。