迷思
首发于迷思
谈谈AWS Lambda和serverless architecture

谈谈AWS Lambda和serverless architecture

最近半年来,随着AWS的各线服务都开始支持lambda,serverless architecture便渐渐成为一个火热的话题。lambda是amzon推出的一个受控的运行环境,起初仅仅支持nodejs(之后添加了java/python的支持)。你可以写一段nodejs的代码,为其创建一个lambda资源,这样,当指定的事件来临的时候,aws的运行时会创建你的运行环境,执行你的代码,执行完毕(或者timeout)后,回收一切资源。这看起来并不稀罕,整个运行环境还受到很多限制,比如目前aws为lambda提供了哪些事件支持,你就能用哪些事件,同时你的代码无法超过timeout指定的时间执行(目前最大是5min),内存使用最多也就是1.5G。那么问题来了,这样一个看起来似乎有那么点鸡肋的服务,为什么还受到如此热捧?原因就在于无比低廉的价格(每百万次请求0.2美元 + 每百万GB秒运行时间16.67美元),毋须操心infrastructure,以及近乎无限扩容的能力。


使用lambda处理事件触发


在服务器端,我们所写的大部分代码是事件触发的:


  • 处理用户对某个URI的请求(打开某个页面,点击某个按钮)

  • 用户注册时发邮件验证邮箱地址

  • 用户上传图片时将图片切割成合适的尺寸

  • 当log持续出现若干次500 internal error时将错误日志聚合发给指定的邮箱

  • 半夜12点,分析一天来收集的数据(比如clickstream)并生成报告

  • 当数据库的某个字段修改时做些事后处理

同时,在处理一个事件的过程中,往往会触发新的事件。基本上我们做一个系统,如果能厘清内部的数据流和事件流,以及对应的行为,那么这个系统的架构也就八九不离十了。如果要让我们自己来设计一个分布式的事件处理系统,一般会使用Message Qeueue,比如RabbitMQ或者Kafka作为事件激发和事件处理的中枢。这往往意味着在现有的infrastructure之外至少添置事件处理的broker(MQ)和worker(读取并处理事件的例程)。如果你用aws的服务,SQS(或者SNS+SQS)可以作为broker,然后配置若干台EC2做worker。如果某个事件流的产生速度大大超过这个事件流的处理速度,那么我们还得考虑使用auto scaling group在queue的长度超过一定阈值或者低于一定阈值时scale up / down。这不仅麻烦,也无法满足某些要求一定访问延迟保障的场景,因为,新的EC2的启动直至在auto scaling group里被标记为可用是数十秒级的动作。


lambda就很好地弥补了这个问题。lambda的执行是置于container之中的,所以启动速度可以低至几十到数百毫秒之间,而且它可以被已知的事件或者某段代码触发,所以基本上你可以在不同的上下文中直接调用或者触发lambda函数,当然也可以使用SNS(kenisis)+lambda取代原本用MQ+worker完成的工作。


我们看上述事件的处理:


  • 处理用户对某个URI的请求(打开某个页面,点击某个按钮):使用API gateway + lambda

  • 用户注册时发邮件验证邮箱地址:


    • 可以在用户注册的流程里直接调用lambda函数发送邮件

    • 如果使用dynamodb,可以配置lambda函数使其使用dynamodb stream在用户数据写入数据时调用lambda

  • 用户上传图片时将图片切割成合适的尺寸


    • 可以配置lambda函数被S3的Object Create Event触发,在lambda函数里使用libMagic的衍生库处理图片。

  • 当log持续出现若干次500 internal error时将错误日志聚合发给指定的邮箱


    • 如果用kenisis来收集log,那么可以配置lambda函数使其使用kenisis stream

  • 半夜12点,分析一天来收集的数据(比如clickstream)并生成报告


    • 使用aws最新支持lambda cronjob

  • 当数据库的某个字段修改时做些事后处理


    • 如果使用dynamodb,同上(配置lambda函数使其使用dynamodb)

    • 如果使用RDBMS,可以使用database trigger + lambda cronjob

想进一步深入代码的童鞋,可以看我的这个repo: tyrchen/aws-lambda-thumbnail · GitHub 。它接收S3的Object Create Event,并对event中所述的图片做resize,代码使用es6完成。为了简便起见,没有使用cloudformation创建/更新lambda function,而是使用了aws CLI(见makefile)。如果想要运行此代码,你需要定义自己的 $(LAMBDA_ROLE),手工创建S3 bucket并将其和lambda函数关联(目前aws cli不支持S3 event)。


使用lambda处理大数据


lambda近乎无限扩容的能力使得我们可以很轻松地进行大容量数据的map/reduce。你可以使用一个lambda函数分派数据给另一个lambda函数,使其执行成千上万个相同的实例。假设在你的S3里存放着过去一年间每小时一份的日志文件,为做security audit,你需要从中找出非正常访问的日志并聚合。如果使用lambda,你可以把访问高峰期(7am-11pm)每两小时的日志,或者访问低谷期每四小时的日志交给一个lambda函数处理,处理结果存入dynamodb。这样会同时运行近千个lambda函数(24 x 365 / 10),在不到一分钟的时间内完成整个工作。同样的事情交给EC2去做的话,单单为这些instance配置网络就让人头疼:instance的数量可能已经超出了子网中剩余IP地址的数量(比如,你的VPC使用了24位掩码)。


同时,这样一个量级的处理所需的花费几乎可以忽略不计。而EC2不足一小时按一小时计费,上千台t2.small运行一小时的花费约等于26美金,相当可观。


使用lambda带来的架构优势


如果说lambda为事件处理和某些大容量数据的快速处理带来了新的思路,并实实在在省下了在基础设施和管理上的真金白银,那么,其在架构上也带来了新的思路和优势。


web系统是天然离散的系统,里面涵盖了众多大大小小,或并联,或串联的子系统。因为基础设施的成本问题,很多时候我们做了逻辑上的分层和解耦,却在物理上将其部署在一起,这为scalability和management都带来了一些隐患。scalability上的隐患好理解,management上的隐患是指这无形把dev和ops分成不同的team:一个个dev team可以和逻辑上的子系统一一对应,但ops却要集中起来处理部署的问题,免得一个逻辑上「解耦」的功能更新,在物理上却影响了整个系统的正常运行。这种混搭的管理架构势必会影响部署的速度和效率,和「一个team负责从功能开发到上线所有的事情」的思路是相悖的。


举个例子:「用户上传图片时将图片切割成合适的尺寸」这一需求可能在不断变化和优化。对于任何失焦的照片我们还希望做一些焦距上的优化,此外,如果上传的是头像,那么我们希望切割的位置是最合适的头像的位置,如果上传的是照片,除了切割外,我们可能还要生成黑白/灰度等等不同主题的图片。这个功能在不改变已有接口的前提下,并不会影响其他团队的工作,但因为和其他功能放在一起部署,所以部署的工作并不能自己说了算。因为部署交由专门的ops团队完成(可能一天部署一次,也可能一周部署一次),这个团队无法很快地把一些有意思的点子拿出来在生产环境试验,拖累了试错和创新。


而lambda解决了基础设施上的问题,每个子系统甚至子功能(小到函数级的粒度)都可以独立部署,这就让功能开发无比轻松。只要界定好事件流的输入输出,任何事件处理的功能本身可以按照自己团队的节奏更新。


部署和管理上的改变反过来会影响架构,促成以micro-service为主体的系统架构。micro-service孰好孰坏目前尚有争论,但micros-ervice不仅拥抱软件设计上的解耦,同时拥抱软件部署上的解耦是不争的事实。一个web系统的成败和其部署方案有着密切的关系,耦合度越低的部署方案,其局部部署更新的能力也就越强,而一个系统越大越复杂,就越不容易整体部署,所以对局部部署的要求也越来越高。这如同一个有机体,其自我更新从来不靠「死亡-重生」,而是通过新陈代谢。


此外,lambda还是一个充分受限的环境,给代码的撰写带来很多约束条件。我之前在谈架构的时候曾经提到,约束条件是好事,设计软件首先要搞明白约束条件。lambda最强的几个约束是:


  • lambda函数必须设计成无状态的,因为其所有状态(内存,磁盘)都会在其短短的生命周期结束后消失

  • lambda函数有最大内存限制

  • lambda函数有最大运行时间限制

这些限制要求你把每个lambda函数设计得尽可能简单,一次只做一件事,但把它做到最好。很符合unix的哲学。反过来,这些限制强迫你接受极简主义之外,为你带来了无限扩容的好处。


JAWS和server-less architecture


两三个月前,我介绍了JAWS,当时它是一个利用aws刚刚推出的API gateway和lambda配合,来提供REST API的工具,如果辅以架设在S3上的静态资源,可以打造一个完全不依赖EC2的网站。这个项目从另一个角度诠释了lambda的巨大威力,所以demo一出炉,就获得了一两千的github star。如今JAWS羽翼臻至丰满,推出了尚处在beta的jaws fraemwork v1版本:jaws-framework/JAWS · GitHub,并且在re:invent 2015上做了相当精彩的主题演讲(见github)。JAWS framework大量使用API gateway,cloudformation和lambda来提供serverless architecture,值得关注。


一个完整的serverless website可以这么考虑:


  • 用户注册使用:API gateway,lambda,dynamodb,SES(发邮件)

  • 用户登录使用:API gateway,lambda,或者(cognito和IAM,如果要集成第三方登录)

  • 用户UGC各种内容:API gateway,lambda,dynamodb

  • 其他REST API:API gateway + lambda

  • 各种事件处理使用lambda

  • 所有的静态资源放在S3上,使能static website hosting,然后通过javascript访问cognito或者REST API

  • 日志存放在cloudWatch,并在需要的时候触发lambda

  • clickstream存在在kenisis,并触发lambda

如此这般,一个具备基本功能的serverless website就搭起来了。


如果你对JAWS感兴趣,可以尝试我生成的 https://github.com/tyrchen/jaws-test。


避免失控


lambda带来的部署上的解耦同时是把双刃剑。成千上万个功能各异的lambda函数(再加上各自不同的版本),很容易把系统推向失控的边缘。所以,最好通过以下手段来避免失控:


  • 为lambda函数合理命名:使用一定规格的,定义良好的前缀(可类比ARN)

  • 使用cloudformation处理资源的分配和部署(可以考虑JAWS)

  • 可视化系统的实时数据流/事件流(类似下图)


(图片来自youtube视频截图:A Day in the Life of a Netflix Engineer,图片和本文关系不大,但思想类似)


由于基于lambda的诸多应用场景还处在刚刚起步的阶段,所以很多orchestration的事情还需要自己做,相信等lambda的使用日趋成熟时,就像docker生态圈一样,会产生众多的orchestration的工具,解决或者缓解系统失控的问题。


如果您觉得这篇文章不错,请点赞。多谢!


欢迎订阅公众号『程序人生』(搜索微信号 programmer_life)。每篇文章都力求原汁原味,北京时间中午12点左右,美西时间下午8点左右与您相会。

文章被以下专栏收录