【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

前言

微服務架構有別於傳統的單體式應用方案,咱們可將單體應用拆分紅多個核心功能。每一個功能都被稱爲一項服務,能夠單獨構建和部署,這意味着各項服務在工做時不會互相影響javascript

這種設計理念被進一步應用,就變成了無服務(Serverless)。「無服務」看似挺荒唐的,其實服務器依舊存在,只是咱們不須要關注或預置服務器。這讓開發人員的精力更集中——只關注功能實現java

Serverless 的典型即是 AWS Lambdanode

AWS Lambda

若是你是 Java 開發人員,你應該據說過或使用過 JDK 1.8 裏面的 Lambda,可是 AWS 中的 Lambda 和 JDK 中的 Lambda 沒有任何關係程序員

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

這裏的 AWS Lambda 就是一種計算服務,無需預置或管理服務器便可運行代碼,藉助 Lambda,咱們幾乎能夠爲任何類型的應用程序或後端服務運行代碼,並且徹底無需管理,咱們要作的只是上傳相應的代碼,Lambda 會處理運行和擴展 HA 代碼所需的一切工做web

說的直白一點sql

Lambda 就比如實現某一個功能的方法 (現實中,一般會讓 Lambda 功能儘量單一),咱們將這個方法作成了一個服務供調用shell

到這裏你可能會有個困惑,Lambda 既然就是一個「方法」,那誰來調用?或怎麼來調用呢?npm

如何調用 Lambda

爲了回答上面這個問題,咱們須要登錄到 AWS,打開 Lambda 服務,而後建立一個 Lambda Function (hello-lambda)json

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

Lambda 既然是個方法,就要選擇相應的 Runtime 環境,以下圖所示,總有一款適合你的(最近在用 Node.js, 這裏就用這個吧)後端

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

點擊右下角的 Create function 按鈕進入配置頁面

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

在上圖紅色框線的位置就能夠配置出發 Lambda 的觸發器了,點擊 Add trigger

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

從上圖能夠看出,AWS 內置的不少服務均可以觸發 Lambda,我在工做中經常使用的有:

  • API Gateway (一會的 demo 會用到,也是最多見的調用方式)
  • ALB - Application Loac Balancer
  • CloudFront
  • DynamoDB
  • S3
  • SNS - Simple Notification Service
  • SQS - Simple Queue Service

上面只是 AWS 內置的一些服務,向下滑動,你會發現,你也能夠配置不少非 AWS 的事件源

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

到這裏,上面的問題你應該已經有了答案了。這裏暫時先無需任何 trigger,先點擊右上角的 Test 測試一下 Lambda

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

一個簡單的 Lambda Function 就實現了,紅色框線的 response 只是告訴你們,每一個請求都會有相應的 Request ID,更有 START/END 標識快速定位 Log 內容 (能夠經過 CloudWatch 查看,這裏暫不展開說明)

你也可能已經開始發散你的思惟了,如何運用 AWS Lambda,其實在 AWS 官網有不少樣例:

經典案例

好比爲了適應多平臺圖片展現,一張原始圖片上傳到 S3 後,會經過 Lambda resize 適應不一樣平臺大小的圖片

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

好比使用 AWS Lambda 和 Amazon API Gateway 構建後端,以驗證和處理 API 請求,當某一個用戶發佈一條動態,訂閱用戶將收到相應的通知

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

接下來咱們就用 Lambda 實現經典的分佈式訂單服務案例

訂單服務 Demo

爲了加強用戶使用體驗,或者爲了提高程序吞吐量,亦或是爲了架構設計程序解耦,考慮到以上這些狀況,咱們一般都會藉助消息中間件來完成

假設有一常見場景,用戶下訂單時若是選擇開具發票,則須要調用發票服務,很顯然調用發票服務不是程序運行的關鍵路徑,這種場景,咱們就能夠經過消息中間件來解耦。這裏有兩個服務:

  1. 訂單服務
  2. 發票服務

若是用 Lambda 來實現兩個服務,總體設計思想就是這樣滴:

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

現實中,咱們不可能在 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 大家有在用嗎?)

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

SF 不但簡化了 AWS 原生 CFT 的編寫,還簡化了跨雲服務的定義,就比如設計模式當中的 Facade,在上面創建了一層門面,隱藏了底部不一樣服務的細節,下降了跨雲並用雲的門檻,目前支持的雲服務有下面這些

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

這裏暫時不會對 SF 展開深刻的說明,在咱們的 demo 中只不過是要應用 SF 來定義

安裝 Serverless Framework

若是你有安裝 Node,那隻須要一條 npm 命令全局安裝便可:

npm update -g serverless

安裝事後檢查一下安裝版本是否成功

sls -version

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

配置 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
  • --provider 雲服務商
  • --key 你的AK
  • --secret 你的SK
  • --profile 若是你有多個帳戶時,你能夠添加這個 profile 作快速區分

運行上述命令後,就會在 ~/.aws/目錄建立一個名爲 credentials 的文件存儲上述配置,就像這樣:

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

到這裏準備工做就都完成了,開始寫咱們的定義就行了

建立 Serverless 應用

經過下面一條命令建立 serverless 應用

sls create --template aws-nodejs --path ./demo --name lambda-sqs-lambda
  • --template 指定建立的模版
  • --path 指定建立的目錄
  • --name 指定建立的服務名稱

運行上述命令後,進入 demo 目錄就是下面這個結構和內容了

➜  demo tree
.
├── handler.js
└── serverless.yml

0 directories, 2 files

由於咱們是用 Node.js 來編寫 Serverless 應用,一樣在 demo 目錄下執行下面命令來初始化該目錄,由於咱們後面要用到兩個 npm package

npm init -y

如今的結構是這樣的(其實就多了一個 package.json):

➜  demo tree
.
├── handler.js
├── package.json
└── serverless.yml

0 directories, 3 files

至此,準備工做都已就緒,接下來就在 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"

乍一看,你可能以爲眼花繚亂,其實這是一個相對完整的 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)} # 引入外部定義的配置變量

config.json 內容僅僅定義了 queue 的名稱,只是爲了說明配置的靈活性

{
  "queueName": "receiverQueue"
}

由於咱們要模擬訂單的生成,這裏用 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"
  }
}

接下來,咱們就能夠編寫兩個 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,
    }),
  };
};

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);
    }
}

到此 demo 的代碼就所有實現了,從中你能夠看到:

咱們沒有關注 lambda 的底層服務細節,沒有關注 sqs 的服務,只是簡單的代碼邏輯實現以及服務之間的串聯定義

最後咱們看一下總體的目錄結構吧:

.
├── app
│   ├── invoice.js
│   └── order.js
├── conf
│   └── config.json
├── package.json
└── serverless.yml

2 directories, 5 files

發佈 Lambda 應用

在發佈以前,編譯一下應用,安裝必須的 package「uuid 和 aws-sdk」

npm install

發佈應用很是簡單,只須要一條命令:

sls deploy -v

運行上述命令後大概須要等帶幾十秒鐘, 在構建的最後,會打印出咱們的構建服務信息:

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

上圖的 endpoints 就是咱們一會要訪問的 API gateway 觸發 lambda 的入口,在調用以前,咱們先到 AWS console 看一下咱們定義的服務

lambda functions

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

SQS-receverQueue

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

API Gateway

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

S3

從上圖的構建信息中你應該還看到一個 S3 bucket 的名稱,咱們並無建立 S3, 這是 SF 自動幫咱們建立,用來存儲 lambda zip package 的

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

測試

調用 API gateway 的 endpoint 來測試 lambda

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

打開 SQS 服務,你會發現,接收到一條消息:

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

接下來咱們看看 Invoice Lambda function 的消費狀況,打開 CloudWatch 查看 log:

【AWS徵文】AWS Lambda 藉助 Serverless Framework,迅速起飛

從 log 中能夠看出程序「耗費」 20 秒後打印了向客戶郵件的 log(郵件也能夠藉助 AWS SES 郵件服務來實現)

至此,一個完整的 demo 就完成了,實際編寫的代碼並無多少,就搞定了這麼緊密的串聯

刪除服務

Lambda 是按照調用次數進行收取費用的,爲了防止形成額外的開銷,demo 結束後一般都會將服務銷燬,使用 SF 銷燬剛剛建立的服務也很是簡單,只須要在 serverless.yml 文件目錄執行這條命令:

sls remove

總結與感覺

AWS Lambda 是 Serverless 的典型,藉助 Lambda 能夠實現更小粒度的「服務」,無需服務搭建也加快了開發速度。Lambda 一樣能夠結合 AWS 不少其服務,接收請求,將計算結果傳遞給下游服務等。另外不少第三方合做夥伴也在加入 Lambda 的 trigger 大部隊,給 Lambda 更多觸發可能,同時,藉助 CI/CD,能夠快速實現功能閉環

開通 AWS free tier,足夠你玩轉 Lambda

相關文章
相關標籤/搜索