前言

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

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

Serverless 的典型便是 AWS Lambda

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

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

sls -version

配置 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 的文件存储上述配置,就像这样:

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

创建 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

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

上图的 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

总结与感受

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

开通 AWS free tier,足够你玩转 Lambda : https://dayarch.top/p/aws-lambda-with-serverless-framework.html

个人博客:https://dayarch.top

加我微信好友, 进群娱乐学习交流,备注「进群」

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


AWS Lambda 借助 Serverless Framework,迅速起飞的更多相关文章

  1. [翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能

    [翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能 原文: Comparing AWS Lambda performance of Node.js, ...

  2. AWS Step Function Serverless Applications

    Installing VS Components To follow along with this article, you must have an AWS account and install ...

  3. AWS Lambda

    AWS Lambda 知识点总结 参考资料:Amazon 名词解释: 事件驱动型计算服务:通过事件来触发的计算服务 Amazon S3存储桶:一项面向Internet的存储服务,可以通过S3 随时在W ...

  4. Automated EBS Snapshots using AWS Lambda & CloudWatch

    Overview In this post, we'll cover how to automate EBS snapshots for your AWS infrastructure using L ...

  5. Qwiklab'实验-API Gateway, AWS Lambda'

    title: AWS之Qwiklab subtitle: 2. Qwiklab'实验-API Gateway, AWS Lambda' date: 2018-09-20 17:29:20 --- In ...

  6. How to return plain text from AWS Lambda & API Gateway

    With limited experience in AWS Lambda & API Gateway, it's struggling to find the correct way to ...

  7. 什么是AWS Lambda?——事件驱动的函数执行环境

    AWS CTO Werner Vogels在AWS re:Invent 2014大会的第二场主题演讲上公布了两个新服务和一系列新的实例,两个新服务都相当令人瞩目:第一个宣布的新服务是Amazon EC ...

  8. [AWS] Lambda by Python

    当前统治数据分析的语言还是Python,还是暂时走:Python + GPU的常规路线好了. numba, pyculib (分装了cublas) Ref: 使用 Python 构建 Lambda 函 ...

  9. 使用AWS Lambda,API Gateway和S3 Storage快速调整图片大小

    https://www.obytes.com/blog/2019/image-resizing-on-the-fly-with-aws-lambda,-api-gateway,-and-s3-stor ...

随机推荐

  1. MyBatis学习(三)日志输出环境配置

    一.编写日志输出环境配置文件 在开发过程中,最重要的就是在控制台查看程序输出的日志信息,在这里我们选择使用 log4j 工具来输出: 准备工作:将[MyBatis]文件夹下[lib]中的 log4j ...

  2. C# NX二次开发环境搭建

    在网上看到一篇C#二次开发环境搭建的文章:NX二次开发-使用NXOPEN C#手工搭建开发环境配置 ,写得非常好.我按照文章操作,过程中遇到几个问题,把问题分享给大家,希望对各位有帮助. 注意三点: ...

  3. Combine 框架,从0到1 —— 5.Combine 中的 Subjects

    本文首发于 Ficow Shen's Blog,原文地址: Combine 框架,从0到1 -- 5.Combine 中的 Subjects. 内容概览 前言 PassthroughSubject C ...

  4. 基础篇:java基本数据类型

    1:java几种基本数据类型大小 关键字 类型 位数 (8位一字节) 取值范围(表示范围) byte 整型 8 -2^7 ~ 2^7-1 short 整型 16 -2^15 ~ 2^15-1 int ...

  5. Centos-bash-4.1$

    错误: -bash-4.1$ where? 登录Centos时候,会显示4行这样的错误信息-bash-4.1$ why? 1. 该用户家目录缺少 .bashrc .bash_logout .base_ ...

  6. Python练习题 039:Project Euler 011:网格中4个数字的最大乘积

    本题来自 Project Euler 第11题:https://projecteuler.net/problem=11 # Project Euler: Problem 10: Largest pro ...

  7. 「面试」拿到B站的意向书

    此次B站服务端开发面试之旅可谓惊险,不过通过对大部分面试题套路的掌握,不出意外还是拿下了,下面我们来看看这些骚题是不是常见的不能再常见的了.这些面试题看了就能面上?当然不是,只是通过这些题让自己知道所 ...

  8. C语言&C++ 中External dependencies

    参考:https://blog.csdn.net/yyyzlf/article/details/4419593 External   Dependencies是说你没有把这个文件加入到这个工程中,但是 ...

  9. Matlab中imagesc用法

    来源:https://ww2.mathworks.cn/help/matlab/ref/imagesc.html?searchHighlight=imagesc&s_tid=doc_srcht ...

  10. matlab中num2str 将数字转换为字符数组

    参考:https://ww2.mathworks.cn/help/matlab/ref/num2str.html?searchHighlight=num2str&s_tid=doc_srcht ...