© 2016, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
龚凌晖,Strikingly.com
2016年9月8日
基于AWS Lambda的无服务器架构
在Strikingly中的应用
Strikingly
简单易用的自助式建站工具 YC孵化的第一个中国团队
云计算的架构演化
从传统IDC到无服务器架构
传统IDC IaaS – 虚拟机 PaaS – 容器 Code-as-a-Service
Function-as-a-Service
AWS Lambda简介
AWS Lambda
基于事件驱动的无服务器计算服务
产品特性
无需管理服务器 事件驱动 自动扩展 精确计费 节约成本
如何开始使用AWS Lambda?
将代码包装为
Lambda函数
选择合适的
运行环境
将代码打包
上传到AWS
事件驱动触发
执行Lambda函数
Strikingly实践案例
案例一:网页静态渲染
场景:网站编辑器
场景:用户网站
问题描述
JS动态绑定数据生成网页
• 代码灵活,便于设计模板
• 网页编辑器和网页重用代码
• 动态加载速度缓慢
• SEO不友好
网页模板设计 用户网页数据
用户网页数据绑定
解决方案(旧)
浏览器
Webhook回调
发布网站
WEB服务器
任务队列
后台服务器
数据库
PhantomJS集群
解决方案(新)
浏览器
发布网站
WEB服务器 后台服务器
数据库
AWS
打包部署工具
Apex (http://apex.run/)
• Build
• Deploy
• Manage
代码示例:project.json
代码示例:index.js
代码示例:static-render.js
方案比较
旧方案 新方案
系统复杂度
较高,需要维护任务缓冲
队列以及PhantomJS集
群
较低,基本没有
额外维护开销
可伸缩性
较弱,PhantomJS集群
容量相对固定
很强,自动伸缩
每月开销
较高,由峰值rps和总请
求数决定
很低,由请求数和请求时
长决定
新方案将开销降低到原来的1/100!
案例二:图像转换服务
场景:用户上传图片
问题描述
图像实时转换
• 伸缩,裁剪
• 翻转,旋转
• 明亮度,对比度
• 预定义滤镜
• 等等
网页编辑器 WEB服务器
图像存储
图像转换服务
访客浏览器
图像转换示例
http://assets.strikinglycdn
.com/images/Lenna.png
http://assets.strikinglycdn.com/images/
c_limit,h_200,w_200,f_auto,q_90/Lenn
a.png
http://assets.strikinglycdn.com/images/
c_limit,h_200,w_200,f_auto,
bo_6px_solid_rgb:000000/Lenna.png
http://assets.strikinglycdn.com/images/
c_crop,h_200,w_200,g_face/Lenna.pn
g
http://assets.strikinglycdn.com/images/
c_limit,h_200,w_200,e_
grayscale/Lenna.png
理想实现方案
网页编辑器 WEB服务器
访客浏览器
S3
Lambda
API GatewayCloudfront
• 访客浏览器:http://assets.strikinglycdn.com/images/c_limit,h_200,w_200,e_ grayscale/Lenna.png
• API Gateway:/images/{transformations}/{object_id}
• Lambda:从event中获取transformations和object_id,读取object并调用图像处理库(如ImageMagick)处理
• Cloudfront: assets.strikinglycdn.com -> xxxxxxx.execute-api.xx-xxxxxxxxx-1.amazonaws.com
但是……
最终实现方案
• 访客浏览器:http://assets.strikinglycdn.com/images/c_limit,h_200,w_200,e_ grayscale/Lenna.png
• Cloudfront: assets.strikinglycdn.com -> strikingly-images-transformed.s3.amazonaws.com
• API Gateway:/images/{manipulations}/{object_id}
• Lambda:从event中获取manipulations和object_id,读取object并调用图像处理库(如ImageMagick)处理并
存放到另一个S3 bucket strikingly-images-transformed里
网页编辑器 WEB服务器
访客浏览器
S3
Lambda
Cloudfront S3
API Gateway
方案比较
第三方服务 理想方案 最终方案
系统维护 无 非常低 非常低
可伸缩性 很强 很强 很强
灵活性 很强 很强 比较强
每月开销
很高,由图片存
储量,图片处理
次数以及CDN流
量决定
较低,由图片存
储量,图片处理
次数以及CDN流
量决定
较低,由图片存
储量,图片处理
次数以及CDN流
量决定
新方案预计将开销降低到原来的1/5!
小结
• API Gateway+Lambda适用于特定场景下的服务
• 业务逻辑清晰且相对独立
• 负载难以预测
• 弹性要求极高
• 解决方案优点
• 高伸缩性
• 高可用性
• 低复杂度
• 低开销
案例三:日志归集处理
问题描述
日志归集处理
• 收集不同服务的日志
• 自动导入到日志系统
• 日志搜索
• 日志信息可视化
• 等等
解决方案
WEB服务器
后端服务器
ELB
CDN服务 FTP
Elasticsearch
LogStash Kibana
小结
• Scheduler定时触发Lambda可用于替代Cron Job
• 无需维护Cron Job服务器
• 通过AWS API管理
• IAM权限设置
• S3+Lambda可用于数据处理及分析
• 及时处理新数据
• 可对S3对象进行任意处理
经验与总结
常见问题
• 我可以在Lambda函数启动的时候安装代码库吗?
• Lambda如何做到在不同流量下快速伸缩?
• Lambda可伸缩性强,是否没有上限?
• 如何方便地发布和回滚Lambda的代码?
• 我的代码依赖于平台相关代码库怎么办?
• 我真的可以部署任何代码到Lambda上去吗?
总结
• AWS Lambda是事件驱动的无服务器计算模型
• 无需初始投入,运维成本几乎为零
• 事件驱动执行,自动扩展,可伸缩性极强
• 运行任何代码,灵活性强,限制在于想象力
• 仅按用量收费,每一分钱都用在刀刃上
欢迎交流AWS相关的技术
• StrikinglyTeam公众号
• 我们会在上面分享
• 技术
• 产品
• 创业
Q & A

基于AWS Lambda的无服务器架构在Strikingly中的应用

Editor's Notes

  • #3 在进入主题之前,我想首先简单介绍一下我们的产品。Strikingly是一个简单易用的自助式建站平台,提供一站式的解决方案,包括模板设计,静态资源,网站编辑器,来帮助用户在非常短的时间内搭建起自己的网站。用户网站搭建完毕之后也会托管在Strikingly的服务器上。 在今天的演讲中,我会首先跟大家简单介绍一下无服务器架构和Lambda,然后通过3个实际的案例来介绍我们如何基于Lambda搭建无服务器的系统架构来支持Strikingly的产品和服务。
  • #4 我们先来回顾一下云计算的架构演化,帮助我们理解什么是无服务器架构,以及为什么我们需要无服务器架构。
  • #5 互联网应用的构建平台经历了从传统IDC,到基于虚拟机技术的IaaS平台,到基于容器技术的PaaS平台三个阶段。大家可以看到,这个演化的过程一直在朝着降低运维成本,提升开发者生产力的方向前进。Strikingly在早期发展阶段使用的Heroku是典型的PaaS服务提供商。PaaS平台可以使我们关注在应用本身,不必关心服务器的配置和应用的部署等下层平台细节,从而提高开发迭代速度。当时我们在没有任何其他运维自动化工具的情况下依然可以每天部署多达十几次,这样快速迭代的能力在创业初期显得尤为重要。 但是,有了PaaS还不够。我们依然需要调节计算资源的数量来适应系统的负载变化。如果计算资源可以随着系统负载变化快速自动伸缩,不需要技术人员干预,那就更好了。无服务器计算的概念正是源于这样的需求。字面上理解,任何不需要服务器的架构都可以算是无服务器的架构,而事实上,我认为完全不需要管理计算资源,才是无服务器架构的极致体现。Lambda就提供了这样一种服务,我们称之为Code as a service或者Function as a service。
  • #6 关于Lambda,我想了解一下,在座的各位: 有多少朋友知道Lambda是什么? 有多少朋友尝试过使用Lambda? 有多少朋友现在在生产环境中使用Lambda?
  • #7 那么Lambda到底是什么?一句话来总结,AWS Lambda是基于事件驱动的无服务器计算服务。
  • #8 这句话里面包含了两个重要的产品特性: 第一,不需要管理服务器,甚至都不需要管理抽象的计算资源 第二,Lambda是事件驱动执行的,并且随着事件触发的频率增加自动扩展计算能力 这句话里面没有包含的第三个非常重要的产品特性就是精确计费。Lambda严格按照使用量收费,计时精确到次秒,没有负载的时候不计费,完全不存在计算资源闲置的浪费。
  • #9 那么如何开始使用Lambda呢?很简单: 第一步:将你现有的代码包装成Lambda函数。Lambda支持Node, Python, Go, Java等几种运行环境和语言接口,但是同时Lambda本身提供了一个Linux的运行环境,所以理论上可以通过不同语言之间的接口调用或者操作系统层面的进程调用运行任何你已有的代码。 第二步:选择Lambda计算单元的大小。Lambda提供了单一维度的指标供开发者选择,你只需要选择需要的运行内存大小,Lambda会自动适配对应的CPU,网络IO等其他运行时系统资源。 第三步:将代码上传到AWS。如果代码足够简单,你可以直接通过AWS控制台输入代码。如果代码有超过一个源文件或者有运行时依赖,你可以把所有代码和依赖打包上传到AWS。 第四步:指定对应的事件触发方式,比如API Gateway请求,S3事件,SNS消息等等。 完成之后,你的Lambda函数就会在指定事件发生时被触发,完成你定义的任务。
  • #10 接下来,我们通过几个Strikingly的实际案例来看看Lambda能解决什么样的问题,以及如何解决这些问题。
  • #11 第一个案例叫做网页静态渲染。
  • #12 这是Strikingly的网站编辑器,左边是控制面板,右边是网页编辑区域。
  • #13 这是Strikingly用户网站的最终显示效果。 大家可以看到,我们提供的是一个所见即所得的网页编辑器,编辑器本质上就是在用户网页的基础上加一层编辑组件。那用户网站是怎么显示出来的呢?
  • #14 简单的来说,Strikingly的用户网站由以下两个部分构成: 网站模板的HTML元素和CSS样式,也就是网页的模板设计 用户在模块上输入的文字,图片,链接等等,也就是用户数据 Strikingly的网站渲染引擎通过执行Javascript代码将用户数据绑定到对应的HTML元素上去,把用户网站渲染出来。这种实现方式代码灵活,便于我们设计更多更丰富的模板。我们的网站编辑器和已发布的网站都采用这种方式对页面进行渲染,这样代码重用度高,从可维护性上来说是一件好事。但是也带来了两个问题: 每次浏览器加载网页都需要执行一遍数据绑定,网页的渲染速度比较慢,在低端设备上有明显的卡顿现象 搜索引擎的爬虫不执行Javascript,这导致搜索引擎获取的网页内容中,模板和数据是分离的,搜索引擎并不能正确地获取网站内容,在它们看来,所有的Strikingly网站都只是模板而已 解决这两个问题的最直观的思路:如果我们可以在客户端获取网页之前就完成数据绑定,并把绑定后的页面保留下来,我们就可以提供已完成绑定的页面给客户端,包括浏览器和爬虫,一箭双雕解决两个问题。
  • #15 在最初的解决方案中,我们使用PhantomJS集群来处理所有的页面渲染请求。(介绍PhantomJS)用户在发布网站的时候,我们的服务器将向集群发送需要渲染的原始页面的URL。页面渲染完毕之后通过Webhook回调的形式返回到服务器,然后服务器会将结果保存在数据库中。 由于PhantomJS集群在固定时间能够处理的请求有限,而用户发布网站的请求频率则是非常不确定且难以预测的,Web服务器需要一个任务队列来缓冲提交的渲染任务,后台服务器则以固定的频率取出任务并发送给PhantomJS集群。当我们观察到任务队列长期处于有任务等待的状态时,我们就知道系统负载已经超出了集群的处理能力,这时就需要在集群中添加计算资源以支持更高的并行处理速度。 我们可以想象,当集群处理能力饱和时,如果我们不能很快地提升它的处理能力,一些网站渲染任务就会处于等待状态,影响用户网站的正常访问和SEO。
  • #16 为了解决这个问题,我们使用AWS Lambda配合API Gateway的方案替换了PhantomJS集群。用户发布网站之后,服务器端把渲染请求发到API Gateway,API Gateway触发Lambda执行,渲染完页面并返回到服务器端,服务器端将结果存到数据库,就完成整个静态渲染过程。 那么这里API Gateway和Lambda是如何替换PhantomJS集群的功能的呢?
  • #17 在讨论细节之前,简单介绍一下我们使用的打包工具Apex。Apex是专为Lambda设计的管理工具,支持打包,部署,版本管理,回滚等功能,可以用来管理一个Lambda函数,也可以有效地管理一系列Lambda函数构成的集成解决方案。 如图所示是这个项目的目录结构,根目录下有项目配置文件。我们用于静态渲染的这个Lambda函数的名称叫做static render,是用Javascript实现的。它的目录下有3个文件,包含一个Lambda的函数入口文件,一个phantomjs的二进制可执行文件,以及一个phantomjs的脚本文件。
  • #18 首先我们看一下Apex的配置文件,一个Apex项目可以包含多个Lambda函数,project.json文件是整个项目级别的配置文件,里面除了定义项目名称和描述之外,还定义了项目默认的Lambda运行环境,计算单元内存大小,超时时间,以及Lambda函数执行时的IAM角色。
  • #19 Index.js是Lambda函数的入口代码,这段代码的功能是在Lambda的计算环境下启动一个PhantomJS子进程来执行我们定义的脚本。Lambda函数进程通过这个子进程的句柄获得它的输出,并将子进程的标准输出作为response通过API Gateway返回到我们的服务器。
  • #20 static-render.js是PhantomJS加载执行的脚本。它做的事情也很简单:通过URL加载原始页面,并在原始页面加载渲染完毕时通过回调把页面的渲染结果写到标准输出。
  • #21 我们来进行一下两个方案的比较: 旧方案的复杂度比较高,需要维护任务缓冲队列和PhantomJS集群;新方案除去配置文件外总共50行代码,可以通过Apex完成一键部署,Lambda本身有Cloudwatch进行监控和日志采集,开发成本低,也几乎没有任何维护开销。 旧方案的可伸缩性较弱;新方案的计算能力可以根据系统负载自动伸缩,无需人工干预。 旧方案中PhantomJS集群的维护成本比较高;新方案中Lambda的费率非常低,经过计算,我们发现开销降低到了原来的1/100。 目前新方案已经在我们生产环境中运行了几个月,已经被证明是非常有效并且经济的解决方案。
  • #22 第二个案例叫做图像转换服务。
  • #23 建站的一个重要的部分就是将合适的图片以合适的尺寸放在合适的位置。我们的图像转换服务的主要功能就是对用户上传的图片进行处理。
  • #24 具体来说,用户在上传图像之后我们会把原始图像存储下来,然后浏览器在加载图像URL的时候,我们图像转换服务可以根据URL对原始图像进行实时处理,转换到所需的结果,然后发送到浏览器端。如果有些图像上传之后没有访问流量,我们系统就不会对它进行任何处理。
  • #25 为了让大家对实时转换有个更直观的概念,我来举个例子。 最左边的图片是原图,右边四张则是通过URL中的转换规则进行处理之后的结果。这些图片都是在访问这些URL的时候实时生成的,并不是预先生成的。
  • #26 我们来设计一下这个服务应该如何实现。 借鉴一下上个案例的经验,我们可以通过API Gateway结合Lambda来对已经上传到S3的图片进行实时转换并返回到浏览器端。API Gateway支持路径变量,因此我们可以把转换规则和原始图片的S3对象ID通过路径变量传给Lambda。Lambda可以根据这个ID读取S3对象,调用图像处理库进行处理,并返回处理后的结果。 由于同一个图片URL会被加载很多次,我们可以在API Gateway之前放置Cloudfront CDN,这样既可以提高图像的访问速度,又缓存了已经转换的图像数据,避免了重复转换。 好像是一个挺完美的解决方案。
  • #27 我们在设计这个方案的时候忽略了一个细节。在实现的过程中我们发现,API Gateway只支持JSON兼容格式输出,并不支持二进制输出,因次无法直接通过API Gateway输出二进制的图像数据。我们查阅资料的过程中看到有不少开发者有类似的需求,也看到AWS的工程师回复说有计划支持二进制输出,但是具体的时间节点则没有明确的说法。因此这个理想的实现方案只能暂时搁置,在此基础上我们提出了另一个不那么完美,但是在我们目前的应用场景下仍然可以解决问题的方案。
  • #28 由于API Gateway的限制,我们只能妥协,将实时转换改为预处理。 也就是说,在图片上传的时候,我们会根据图片上传的上下文环境决定这张图片可能会有哪些需要做的转换处理,当图片上传到S3之后,我们的系统会发送请求到API Gateway,触发Lambda对该图片的转换,并将转换结果存到另一个S3 bucket里去。当图片的URL被访问的时候,通过Cloudfront返回的是S3中已经经过预处理的图片。 由于每张图片在编辑器中上传的上下文环境决定了该图片是作为背景图还是普通图片插入网页,每个上下文环境中所需的转换规则也只有少数几种,这种预处理的方式还是可以解决问题的。
  • #29 我们来比较一下三个方案: 我们目前在使用一个第三方服务来完成这个功能,但这意味着我们必须使用他们定义的图像转换API,如果其中没有包含我们需要的转换操作,我们就无法使用。另外,我们在这个第三方服务上的开销非常大,长远来看不是最合理的选择。 我们设想的理想方案具备非常强的可伸缩性和灵活性,可以自己定义图像转换API,相比于第三方服务,开销可以大大降低,虽然需要自己开发和长期维护,但是对于Lambda来说,开发和长期维护的成本都很低,带来的效益却是相当可观的。 我们的最终方案虽然目前不完美,但是同样可以自定义图像转换API,开销预计可以降低到原来的1/5,等到API Gateway支持二进制输出之后稍作修改就能变成理想方案,因此仍然不失为一种合理的解决方案。
  • #30 我们来对前两个案例做一下小结。 一般来说,API Gateway加Lambda适用于业务逻辑清晰并且相对独立的服务模块。尤其是如果该模块的负载波动非常大,难以预测,对于弹性要求非常高,那API Gateway加Lambda的优势就非常明显了。而对于那些API节点之间业务逻辑联系紧密,有状态关联的应用,这种解决方案就不太适用。
  • #31 我们来看一下第三个案例叫做日志归集处理。
  • #32 作为一个较为复杂的系统,Strikingly内部有各种组件,服务以及外部第三方服务都会产生日志。我们需要一个集中式的日志系统把相关联的日志归集到一起,并在此基础上实现日志搜索和数据可视化,方便技术团队的问题查找和市场团队的数据提取。那么Lambda在这个系统起到什么作用呢?
  • #33 比如说,我们有CDN服务会产生日志,但是CDN服务并没有API可以导出日志,只能把日志上传到指定的FTP服务器供我们下载。这个时候我们就需要通过Cloudwatch的定时器发送事件到Lambda,周期性地驱动Lambda函数登录FTP服务器并把最新的日志下载到我们指定的S3 bucket上做一个存档,方便后续处理。AWS ELB会自动把日志存档到指定的S3 bucket上去,所以这里我们不需要做什么工作。同时,我们可以利用S3的事件机制触发另一个Lambda函数,当这些存放日志的bucket加入新的对象时,Lambda函数就会执行,读取日志文件并导入到LogStash里。然后LogStash会把这些日志跟服务器直接发送过来的日志一起做预处理后导入ElasticSearch,并通过Kibana提供日志搜索和数据可视化服务。
  • #34 这里对于案例三做一下小结,我们可以看到这里涉及了两种触发Lambda的方式: 第一种是Cloudwatch Schedule事件。通过这种方式触发Lambda可以替代cron job,执行周期性的工作。同时它带来的好处还包括无需为cron job维护服务器,可以通过AWS API自动管理Cloudwatch Scheduler和Lambda函数,并且执行的代码需要遵循Lambda的IAM权限设置,权限管理更加方便。 第二种是S3对象事件。S3对象事件可以使Lambda及时处理新数据,Lambda本身的灵活性决定了可以对S3对象进行任意处理而不仅限于AWS提供的服务,比如图像转换,比如日志预处理等等。这里要注意的是,如果S3对象体积太大或者需要的处理时间过长的话,应该由Lambda交给下游服务处理,Lambda在这里仅仅起到触发和传递作用。
  • #35 Lambda相比于AWS的其他服务来说是一种全新的体验,在摸索的过程中我们做了不少功课也碰了一些钉子,这里有一些经验想跟大家分享一下。
  • #36 我把大家可能比较关心的问题都列了出来: 我可以在Lambda函数启动的时候安装代码库吗? 1.5m 理论上可以这么做,但是实践中强烈不建议这样做。Lambda是无状态的计算模型,这意味着每一次Lambda函数的运行都跟之前没有任何依赖和关联,因此每一次运行都需要重新安装代码库,严重影响Lambda的运行效率。所有代码运行过程中需要依赖的代码库及资源尽量都在部署的时候就打包上传,只有跟每一次触发的Event相关的资源才在运行时去获取,这样可以有效地保证Lambda的运行时间不被浪费。 Lambda如何做到在流量变化下快速伸缩? 1.5m Lambda内部实现机制上包括了有效地流量预测算法,从而可以对流量增长进行预判并提前准备好相应的计算资源。这个流量预测算法有一个无法避免的盲点,就是在没有流量的情况下,没有任何依据进行预测,因此会导致Lambda函数在一段时间静默之后第一次启动的时候会有延时。如果你的服务可以接受轻微的延时,那这并不一定是个大问题,但如果你的服务实时性要求比较高,那就需要保持Lambda函数在一个热身状态。这个保持的方法就是用scheduler定时(比如每10秒钟)触发一次Lambda的执行。 Lambda可伸缩性强,是否没有上限? 1.5m Lambda默认的并行执行的上限为100,这个上限是Region的上限,也就是说你在某个Region里某个瞬间同时运行的所有Lambda实例的数目不能超过100,否则就需要排队等候。这是AWS为了保护客户在刚开始使用Lambda的时候不会因为一不小心犯了个错误(比如递归执行同一个Lambda函数)导致费用暴增。你可以通过提交ticket的方式来提升上限,强烈建议在项目正式上线之前估算可能的峰值流量并把上线提到相应的值。 如何方便地发布和回滚Lambda的代码? 1.5m Lambda内置了版本和别名机制,可以很方便地发布新版本的代码和回滚到老版本的代码。对于上传的每一版本的代码我们都需要添加版本号,如果你使用Apex的话它会自动添加版本号,比较方便。这里要注意的是,不要直接在某个版本的函数上设置触发条件和监控,而是要充分利用别名机制,在别名上设置触发条件和监控,然后将别名指向你需要发布的版本。比如我们设置了PRODUCTION和TEST等别名,假设我们现在新版本的代码版本号是16,我们会先把TEST指向16,做测试,然后再把PRODUCTION指向16,就完成了新版本的发布。如果新版本有问题的话,我们可以把PRODUCTION指向15,就完成了回滚,非常简单。 我的代码依赖于平台相关代码库怎么办? 1.5m 没有问题。Lambda优点之一就是几乎可以运行任何你已有的代码。虽然Lambda的接口只支持几种语言及版本,但如果通过这些语言来调用其他语言写的代码,甚至平台相关的代码库和二进制可执行文件,就具备了无限的可能性。对于平台相关的代码库和二进制可执行文件,我们需要在与Lambda相同的运行环境下进行编译和链接,或者采用交叉编译的方式针对Lambda的运行平台进行编译和链接,然后打包上传到AWS。AWS对于每个支持Lambda的Region都指定了Lambda运行环境的AMI ID,只需要用对应的AMI启动一个EC2就可以,具体可以参考Lambda的官方文档。 我真的可以部署任何代码到Lambda上去吗? 1.5m Lambda确实鼓励并在技术上能支持任意代码的部署,但是为了保证Lambda的快速伸缩的特性,还是设置了一些限制,比如上传的代码压缩后不能超过50MB,解压后不能超过250MB,运行环境中的临时空间不超过500MB,最长运行时间不超过5分钟,等等。因此Lambda并不适合部署大型应用,任何启动时间长,或者代码和资源量比较大的应用都不适合部署在Lambda上。对于API来讲,确实可以把所有API节点拆开来,每个节点对应一个Lambda函数,理论上是可以实现的,但实际上,如果API节点的数目过多,也会带来管理上的不便以及API函数之间共享代码的额外负担。如果API节点的数目不大,而每个API的请求频次很高或者波动很大,那用Lambda来实现是比较合适的。