Serverless之Framework——图文玩转 AWS Lambda

服务器
前言微服务架构有别于传统的单体式应用方案,我们可将单体应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作时不会互相影响

 前言

微服务架构有别于传统的单体式应用方案,我们可将单体应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作时不会互相影响

这种设计理念被进一步应用,就变成了无服务(Serverless)。「无服务」看似挺荒唐的,其实服务器依旧存在,只是我们不需要关注或预置服务器。这让开发人员的精力更集中——只关注功能实现

Serverless 的典型便是 AWS Lambda

[[345827]]

AWS Lambda

如果你是 Java 开发人员,你应该听说过或使用过 JDK 1.8 里面的 Lambda,但是 AWS 中的 Lambda 和 JDK 中的 Lambda 没有任何关系

这里的 AWS Lambda 就是一种计算服务,无需预置或管理服务器即可运行代码,借助 Lambda,我们几乎可以为任何类型的应用程序或后端服务运行代码,而且完全无需管理,我们要做的只是上传相应的代码,Lambda 会处理运行和扩展 HA 代码所需的一切工作

说的直白一点

Lambda 就好比实现某一个功能的方法 (现实中,通常会让 Lambda 功能尽可能单一),我们将这个方法做成了一个服务供调用

到这里你可能会有个困惑,Lambda 既然就是一个「方法」,那谁来调用?或怎么来调用呢?

如何调用 Lambda

为了回答上面这个问题,我们需要登陆到 AWS,打开 Lambda 服务,然后创建一个 Lambda Function (hello-lambda)

 

Lambda 既然是个方法,就要选择相应的 Runtime 环境,如下图所示,总有一款适合你的(最近在用 Node.js, 这里就用这个吧)

 

点击右下角的 Create function 按钮进入配置页面

 

在上图红色框线的位置就可以配置出发 Lambda 的触发器了,点击 Add trigger

 

从上图可以看出,AWS 内置的很多服务都可以触发 Lambda,我在工作中常用的有:

API Gateway (一会的 demo 会用到,也是最常见的调用方式)

  • ALB - Application Loac Balancer
  • CloudFront
  • DynamoDB
  • S3
  • SNS - Simple Notification Service
  • SQS - Simple Queue Service

上面只是 AWS 内置的一些服务,向下滑动,你会发现,你也可以配置很多非 AWS 的事件源

 

到这里,上面的问题你应该已经有了答案了。这里暂时先无需任何 trigger,先点击右上角的 Test 测试一下 Lambda

 

一个简单的 Lambda Function 就实现了,红色框线的 response 只是告诉大家,每个请求都会有相应的 Request ID,更有 START/END 标识快速定位 Log 内容 (可以通过 CloudWatch 查看,这里暂不展开说明)

你也可能已经开始发散你的思维了,如何运用 AWS Lambda,其实在 AWS 官网有很多样例:

经典案例

比如为了适应多平台图片展示,一张原始图片上传到 S3 后,会通过 Lambda resize 适应不同平台大小的图片

 

比如使用 AWS Lambda 和 Amazon API Gateway 构建后端,以验证和处理 API 请求,当某一个用户发布一条动态,订阅用户将收到相应的通知

 

接下来我们就用 Lambda 实现经典的分布式订单服务案例

订单服务 Demo

为了增强用户使用体验,或者为了提升程序吞吐量,亦或是为了架构设计程序解耦,考虑到以上这些情况,我们通常都会借助消息中间件来完成

假设有一常见场景,用户下订单时如果选择开具发票,则需要调用发票服务,很显然调用发票服务不是程序运行的关键路径,这种场景,我们就可以通过消息中间件来解耦。这里有两个服务:

  1. 订单服务
  2. 发票服务

如果用 Lambda 来实现两个服务,整体设计思想就是这样滴:

 

现实中,我们不可能在 AWS console 通过点击按钮来创建各个服务的,在 AWS 实际开发中, 我们通过写 CloudFormation Template (以下会简称 CFT,其实就是一种 YAML 或者 JSON 格式的定义)来创建相关 AWS 服务,如果上述这个 Demo,从图中可以看出,我们要创建的服务还是非常多的:

  • Lambda * 2
  • API Gateway
  • SQS

如果写 AWS 原生的 CFT,要实现的内容还是挺多的

但是...... 懒惰的程序员总是能带来很多惊喜

Serverless Framework

写 JDBC 麻烦,就有了各种持久层框架的出现,同样写 AWS 原生 CFT 麻烦,就有了 Serverless Framework (以下会简称 SF)的出现帮助我们定义相关 Serverless 组件 (顺便问一下,GraphQL 你们有在用吗?)

 

SF 不但简化了 AWS 原生 CFT 的编写,还简化了跨云服务的定义,就好比设计模式当中的 Facade,在上面建立了一层门面,隐藏了底部不同服务的细节,降低了跨云并用云的门槛,目前支持的云服务有下面这些

 

这里暂时不会对 SF 展开深入的说明,在我们的 demo 中只不过是要应用 SF 来定义

安装 Serverless Framework如果你有安装 Node,那只需要一条 npm 命令全局安装即可:

npm update -g serverless 
  • 1.

安装过后检查一下安装版本是否成功

sls -version 
  • 1.

 

配置 Serverless Framework

由于要使用 AWS 的 Lambda,所以要对 SF 做基本的配置,至少要让 SF 有权限创建 AWS 服务,当你创建一个 AWS 用户时,你可以获取 AK 「access_key_id」和 SK 「secret_access_key」(不是 SKII 哦),其实就是一种用户名和密码形式

然后通过下面一条命令添加配置就可以了:

serverless config credentials --provider aws --key 1234 --secret 5678 --profile custom-profile 
  • 1.
  • --provider 云服务商
  • --key 你的AK
  • --secret 你的SK
  • --profile 如果你有多个账户时,你可以添加这个 profile 做快速区分

运行上述命令后,就会在 ~/.aws/目录创建一个名为 credentials 的文件存储上述配置,就像这样:

 

到这里准备工作就都完成了,开始写我们的定义就好了

创建 Serverless

应用通过下面一条命令创建 serverless 应用

sls create --template aws-nodejs --path ./demo --name lambda-sqs-lambda 
  • 1.
  • --template 指定创建的模版
  • --path 指定创建的目录
  • --name 指定创建的服务名称

运行上述命令后,进入 demo 目录就是下面这个结构和内容了

➜  demo tree 

├── handler.js 
└── serverless.yml 
 
0 directories, 2 files 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

因为我们是用 Node.js 来编写 Serverless 应用,同样在 demo 目录下执行下面命令来初始化该目录,因为我们后面要用到两个 npm package

npm init -y 
  • 1.

现在的结构是这样的(其实就多了一个 package.json):

➜  demo tree 

├── handler.js 
├── package.json 
└── serverless.yml 
 
0 directories, 3 files 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

至此,准备工作都已就绪,接下来就在 serverless.yml 中写相应的定义就可以了 (门槛很低:按照相应的 key 写 YAML 即可,是不是很简单?),打开 serverless.yml 文件来看一下,瞬间懵逼?

# Welcome to Serverless! 

# This file is the main config file for your service. 
# It's very minimal at this point and uses default values
# You can always add more config options for more control. 
# We've included some commented out config examples here. 
# Just uncomment any of them to get that config option

For full config options, check the docs: 
#    docs.serverless.com 

# Happy Coding! 
 
service: lambda-sqs-lambda 
# app and org for use with dashboard.serverless.com 
#app: your-app-name 
#org: your-org-name 
 
# You can pin your service to only deploy with a specific Serverless version 
Check out our docs for more details 
# frameworkVersion: "=X.X.X" 
 
provider: 
  name: aws 
  runtime: nodejs12.x 
 
# you can overwrite defaults here 
#  stage: dev 
#  region: us-east-1 
 
# you can add statements to the Lambda function's IAM Role here 
#  iamRoleStatements: 
#    - Effect: "Allow" 
#      Action
#        - "s3:ListBucket" 
#      Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  } 
#    - Effect: "Allow" 
#      Action
#        - "s3:PutObject" 
#      Resource: 
#        Fn::Join
#          - "" 
#          - - "arn:aws:s3:::" 
#            - "Ref" : "ServerlessDeploymentBucket" 
#            - "/*" 
 
# you can define service wide environment variables here 
#  environment: 
#    variable1: value1 
 
# you can add packaging information here 
#package: 
#  include: 
#    - include-me.js 
#    - include-me-dir/** 
#  exclude: 
#    - exclude-me.js 
#    - exclude-me-dir/** 
 
functions: 
  hello: 
    handler: handler.hello 
#    The following are a few example events you can configure 
#    NOTE: Please make sure to change your handler code to work with those events 
#    Check the event documentation for details 
#    events: 
#      - http: 
#          path: users/create 
#          method: get 
#      - websocket: $connect 
#      - s3: ${env:BUCKET} 
#      - schedule: rate(10 minutes) 
#      - sns: greeter-topic 
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 
#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx 
#      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx 
#      - iot: 
#          sql: "SELECT * FROM 'some_topic'" 
#      - cloudwatchEvent: 
#          event: 
#            source: 
#              - "aws.ec2" 
#            detail-type: 
#              - "EC2 Instance State-change Notification" 
#            detail: 
#              state: 
#                - pending 
#      - cloudwatchLog: '/aws/lambda/hello' 
#      - cognitoUserPool: 
#          pool: MyUserPool 
#          trigger: PreSignUp 
#      - alb: 
#          listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ 
#          priority: 1 
#          conditions: 
#            host: example.com 
#            path: /hello 
 
#    Define function environment variables here 
#    environment: 
#      variable2: value2 
 
# you can add CloudFormation resource templates here 
#resources: 
#  Resources: 
#    NewResource: 
#      Type: AWS::S3::Bucket 
#      Properties: 
#        BucketName: my-new-bucket 
#  Outputs: 
#     NewOutput: 
#       Description: "Description for the output" 
#       Value: "Some output value" 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.

乍一看,你可能觉得眼花缭乱,其实这是一个相对完整的 Lambda 配置全集,我们不需要这么详细的内容,不过这个文件作为我们的参考

接下来我们就定义 demo 所需要的一切 (关键注释已经写在代码中)

service: 
  name: lambda-sqs-lambda # 定义服务的名称 
 
provider: 
  name: aws # 云服务商为 aws 
  runtime: nodejs12.x # 运行时 node 的版本 
  region: ap-northeast-1 # 发布到 northeast region,其实就是东京 region 
  stage: dev # 发布环境为 dev 
  iamRoleStatements: # 创建 IAM role,允许 lambda function 向队列发送消息 
    - Effect: Allow 
      Action
        - sqs:SendMessage 
      Resource: 
        - Fn::GetAtt: [ receiverQueue, Arn ] 
       
functions: # 定义两个 lambda functions 
  order
    handler: app/order.checkout # 第一个 lambda function 程序入口是 app 目录下的 order.js 里面的 checkout 方法 
    events: # trigger 触发器是 API Gateway 的方式,当接收到 /order 的 POST 请求时触发该 lambda function 
      - http: 
          method: post 
          path: order 
 
  invoice: 
    handler: app/invoice.generate # 第二个 lambda function 程序入口是 app 目录下的 invoice.js 里面的 generate 方法 
    timeout: 30 
    events: # trigger 触发器是 SQS 服务,消息队列有消息时触发该 lambda function 消费消息 
      - sqs: 
          arn: 
            Fn::GetAtt: 
              - receiverQueue 
              - Arn 
resources: 
  Resources: 
    receiverQueue: # 定义 SQS 服务,也是 Lambda 需要依赖的服务 
      Type: AWS::SQS::Queue 
      Properties: 
        QueueName: ${self:custom.conf.queueName} 
 
# package: 
#   exclude: 
#     - node_modules/** 
 
custom:  
  conf: ${file(conf/config.json)} # 引入外部定义的配置变量 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

config.json 内容仅仅定义了 queue 的名称,只是为了说明配置的灵活性


  "queueName""receiverQueue" 

  • 1.
  • 2.
  • 3.

因为我们要模拟订单的生成,这里用 UUID 来模拟订单号,

因为我们要调用 AWS 服务API,所以要使用 aws-sdk,

所以要安装这两个 package (这两个理由够充分吗?)


  "name""lambda-sqs-lambda"
  "version""1.0.0"
  "description""demo for lambda"
  "scripts": { 
    "test""echo \"Error: no test specified\" && exit 1" 
  }, 
  "license""MIT"
  "dependencies": { 
    "uuid""^8.1.0" 
  }, 
  "devDependencies": { 
    "aws-sdk""^2.6.15" 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

接下来,我们就可以编写两个 Lambda function 的代码逻辑了

Order Lambda Function订单服务很简单,接收一个下单请求,下单成功后快速返回给用户,同时将订单下单成功的消息发送到 SQS 中,供下游发票服务开具发票使用

'use strict'
 
const config = require('../conf/config.json'
const AWS = require('aws-sdk'); 
const sqs = new AWS.SQS(); 
const { v4: uuidv4 } = require('uuid'); 
 
module.exports.checkout = async (event, context, callback) => { 
    console.log(event) 
    let statusCode = 200 
    let message 
 
    if (!event.body) { 
        return { 
        statusCode: 400, 
        body: JSON.stringify({ 
            message: 'No order body was found'
        }), 
        }; 
    } 
 
    const region = context.invokedFunctionArn.split(':')[3] 
    const accountId = context.invokedFunctionArn.split(':')[4] 
    const queueName = config['queueName'
 
    // 组装 SQS 服务的 URL 
    const queueUrl = `https://sqs.${region}.amazonaws.com/${accountId}/${queueName}` 
    const orderId = uuidv4() 
 
    try { 
       // 调用 SQS 服务 
        await sqs.sendMessage({ 
            QueueUrl: queueUrl, 
            MessageBody: event.body, 
            MessageAttributes: { 
                orderId: { 
                    StringValue: orderId, 
                    DataType: 'String'
                }, 
            }, 
        }).promise(); 
 
        message = 'Order message is placed in the Queue!'
 
  } catch (error) { 
    console.log(error); 
    message = error; 
    statusCode = 500; 
  } 
 
  // 快速返回订单 ID 
  return { 
    statusCode, 
    body: JSON.stringify({ 
      message, orderId, 
    }), 
  }; 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

Invoice Lambda Function

发票服务逻辑同样很简单,消费 SQS 指定队列中的消息,并将开具出的发票发送到客户订单信息的 email 中

module.exports.generate = (event, context, callback) => { 
    console.log(event) 
    try { 
        for (const record of event.Records) { 
          const messageAttributes = record.messageAttributes; 
          console.log('OrderId is  -->  ', messageAttributes.orderId.stringValue); 
          console.log('Message Body -->  ', record.body); 
          const reqBody = JSON.parse(record.body) 
          // 睡眠 20 秒,模拟生成发票的耗时过程 
          setTimeout( () => { 
              console.log("Receipt is generated and sent to :" + reqBody.email) 
          }, 20000) 
        } 
    } catch (error) { 
        console.log(error); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

到此 demo 的代码就全部实现了,从中你可以看到:

我们没有关注 lambda 的底层服务细节,没有关注 sqs 的服务,只是简单的代码逻辑实现以及服务之间的串联定义

最后我们看一下整体的目录结构吧:


├── app 
│   ├── invoice.js 
│   └── order.js 
├── conf 
│   └── config.json 
├── package.json 
└── serverless.yml 
 
2 directories, 5 files 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

发布 Lambda 应用

在发布之前,编译一下应用,安装必须的 package「uuid 和 aws-sdk」

npm install 
  • 1.

发布应用非常简单,只需要一条命令:

sls deploy -v 
  • 1.

运行上述命令后大概需要等带几十秒钟, 在构建的最后,会打印出我们的构建服务信息:

上图的 endpoints 就是我们一会要访问的 API gateway 触发 lambda 的入口,在调用之前,我们先到 AWS console 看一下我们定义的服务

lambda functions

SQS-receverQueue

 

API Gateway

 

S3

从上图的构建信息中你应该还看到一个 S3 bucket 的名称,我们并没有创建 S3, 这是 SF 自动帮我们创建,用来存储 lambda zip package 的

 

测试

调用 API gateway 的 endpoint 来测试 lambda

 

打开 SQS 服务,你会发现,接收到一条消息:

接下来我们看看 Invoice Lambda function 的消费情况,打开 CloudWatch 查看 log:

从 log 中可以看出程序“耗费” 20 秒后打印了向客户邮件的 log(邮件也可以借助 AWS SES 邮件服务来实现)

至此,一个完整的 demo 就完成了,实际编写的代码并没有多少,就搞定了这么紧密的串联

删除服务

Lambda 是按照调用次数进行收取费用的,为了防止造成额外的开销,demo 结束后通常都会将服务销毁,使用 SF 销毁刚刚创建的服务也非常简单,只需要在 serverless.yml 文件目录执行这条命令:

sls remove 
  • 1.

总结与感受AWS Lambda 是 Serverless 的典型,借助 Lambda 可以实现更小粒度的“服务”,无需服务搭建也加快了开发速度。Lambda 同样可以结合 AWS 很多其服务,接收请求,将计算结果传递给下游服务等。另外很多第三方合作伙伴也在加入 Lambda 的 trigger 大部队,给 Lambda 更多触发可能,同时,借助 CI/CD,可以快速实现功能闭环

本文转载自微信公众号「 日拱一兵」,可以通过以下二维码关注。转载本文请联系 日拱一兵公众号。

 

责任编辑:武晓燕 来源: 日拱一兵
相关推荐

2016-07-01 16:13:13

AWSLambda

2016-10-27 13:46:23

AWSLambdaServerless

2018-05-17 22:55:48

AWS Lambda服务器代码

2014-11-14 10:16:18

亚马逊

2014-11-14 10:08:07

AWSAWS Lambda

2019-05-07 09:00:40

无服务器Lambda管理

2023-05-04 17:20:54

AWS ECSAWS Lambda云计算

2015-11-16 17:48:00

2025-02-14 15:36:05

2023-07-30 15:00:21

2010-01-05 14:45:58

.NET Framew

2016-02-22 11:39:27

亚马逊AWS大数据

2018-05-07 10:04:55

ServerlessGoogle ClouAzure

2021-04-21 06:15:28

Serverless 云开发 Todo 案例

2025-02-20 08:11:52

2023-02-07 16:36:34

机器学习Docker无服务器

2014-11-18 09:43:29

AWS Lambda事件驱动计算服务

2022-12-06 08:00:16

awscli工具监控

2013-07-25 15:19:23

iOS开发学习Xcode打包framiOS开发

2016-06-13 15:53:34

SDN开放网络操作系统ONOS
点赞
收藏

51CTO技术栈公众号