最近一则IT行业的新闻引起了广泛传播,非常引人注目:“从微服务转为单体架构,成本降低 90%”。
新闻报道了亚马逊Prime Video团队的案例,他们通过将一个监控系统从基于AWS Lambda的无服务器架构迁移到传统的单体架构,大幅度降低了基础设施成本。
图片来源网络
这则新闻引起轰动的原因可能有几个方面。首先,"90%成本降低"在当前注重成本效益的时代引起了许多行业的共鸣。在宏观经济不确定性的背景下,许多IT组织已将工作重心从技术创新转向了降低运营成本。
其次,该案例讲述了从AWS Lambda架构迁移出去的过程。要知道,AWS最初推出Lambda和无服务器架构时,强调的卖点就是降低计算成本,然而在这个案例中,Lambda的成本竟然不如更简单的单体架构。最具讽刺意味的是,这个案例是由亚马逊自己的Prime Video团队发布的,宛如大水冲了龙王庙。
云计算领域的先驱,37signals的CTO DHH也发表文章嘲笑,连亚马逊自己都不知道如何正确地使用无服务器架构和微服务。
图片来源网络
关于AWS Lambda和无服务器架构,Thoughtworks技术雷达早在2015年就开始追踪和评估其发展,到2016年将其评级从评估提升为试验,然而直到2017年后,该评级一直未能提升到采纳环。也就是说,我们认为AWS Lambda和无服务器架构已具备生产级别的经验,但尚未成熟到默认使用的程度。
AWS Lambda的潜力和困境
AWS Lambda最初的诞生具备了巨大的潜力和颠覆性。在此之前,云服务提供的计算抽象仍然是粗粒度的IaaS和PaaS服务。AWS Lambda允许开发者将函数级别的计算单元部署和托管在云平台上。这极大地降低了计算资源的粒度,增加了计算的弹性。
AWS Lambda提供的降本增效价值主张也非常合理:传统的云计算服务需要按照整个应用级别进行部署和托管,占用更多的资源,而最低计费单位通常是按天计费。而AWS Lambda的计费则根据函数调用的次数和执行时间进行计费,没有最低计费时间的限制。也就是说,如果你的代码只执行了100毫秒,那么只按照100毫秒进行计费,实现真正的按需收费。
这项技术看起来确实是一项具有跨时代意义的云计算技术。首先,相较于传统的IaaS和PaaS,它更为简单,程序员无需进行云服务器的配置和管理,只需编写和部署代码即可。
其次,由于计算的颗粒度在函数级别,使得Lambda的云服务更具弹性,当流量增加时,无需扩容整个应用,只需对高频调用的函数进行自动扩容。最后,按需使用理论上更为成本有效。尽管技术人员清楚,与普通应用进程内的函数调用相比,AWS Lambda的函数调用开销更大,但这是局部优化与全局优化的对比,如果能实现全局层面的函数调用按需计费,理论上可以实现整体成本的降低。
然而,如果这项技术真的如此理想,就不会出现前文提到的新闻了。我们必须承认,AWS Lambda和无服务器架构在实践中面临许多问题。
对于只包含几个Lambda函数和几十行代码的无服务器应用来说,当然非常简单。然而,遗憾的是这种简单的应用通常只存在于演示中。大多数现代应用更加复杂,这带来了许多挑战。首先是代码管理。在传统的大型软件架构中,我们有成熟的实践来管理代码的依赖和复用,例如通过架构分层、提取共享库等。在应用进程内部调试代码相对简单,但一旦涉及跨进程的Lambda调用,追踪和调试就变得更加困难。
其次是可扩展性。AWS Lambda提供了理论上的函数级计算资源弹性。然而,当应用变得复杂时,挑战就超出了人类架构设计的能力。特别是在工作流程编排变得过于复杂时,很容易触发著名的Lambda弹球反模式(Lambda Pinball)。Lambda Pinball是Thoughtworks技术雷达在2019年提出的一个反模式,指的是那些编排混乱的Lambda函数,请求在函数之间像弹球一样来回跳动。这种反模式很容易引发意想不到的副作用、服务之间的竞争条件,并给系统增加更多负载。
更重要的是,当你的复杂性带来更多的性能负载,很多Lambda看起来在执行,实际却没有运行,这个时候成本会指数级爆炸。Lambda函数曾经的最长执行时间是5分钟,执行超时就会被销毁,如今这个时间已经被提升到了15分钟。这给程序员带来更多便利的同时,也降低了对资源浪费的警惕性。最后你会发现,看起来美好的无服务器架构,最后总体成本反而比传统应用大很多。
微服务架构和无服务器架构
我说这个新闻是党,因为AWS Lambda Serverless架构和微服务架构在颗粒度上还是有比较大的区别的。2015年我还在行业里做微服务架构咨询的时候就经常有争论,微服务应该有多微? 我发现很多人会把微服务颗粒度想象的过于小,百十行代码就当成一个微服务,而当初的基础设施成熟度是无法承担这个开销的。所以我不得不在客户那里一遍遍普及,微服务首先是足够大的,但这个大的边界不应该超出两个披萨大的团队(即团队成员规模在两个披萨的饭量,后来我们发现这叫认知负载),也不应该影响特性层面的独立发布和资源弹性。
后来Gartner针对microservice又提了一个概念叫nanoservice,针对的就是这种百十行代码级别的服务。这种nanoservice颗粒度按当时的基础设施成熟度来说是彻底的反模式,后来随着AWS Lambda的普及成为了可能。
当然,无论是微服务架构还是无服务器架构,在技术雷达上从来都没进过采纳环。我们的CTO Rebecca 2018年的时候还发表过文章并接受采访,为什么最早提出微服务架构概念的Thoughtworks反而没有把微服务列入技术雷达采纳环,并且可能一直不会。
其实对于软件架构风格我们可以按照物理的分布式和逻辑的模块化两个维度来去看待。一直以来被架构师们批斗的对象,被微服务架构和无服务武器架构们革命的对象,正是大泥球架构,物理和逻辑的耦合都非常重。
而我们刚才提到的Lambda pinball反模式,其实落入了分布式单体。看起来物理进程已经拆分成分布式了,但逻辑上还是高度耦合的。某种程度上分布式单体甚至比大泥球架构更加糟糕,因为你承受着分布式应用的额外开销,却因为逻辑没解耦没有享受到分布式应用的好处。
前面新闻里的亚马逊Prime Video团队,以及吵着要下公有云的37 signlas,则走向了模块化单体。当应用的复杂度和规模没有到很大的时候,其实通过模块化单体就能大幅提高灵活度和可扩展性,并不一定需要走分布式的路线。甚至对一些性能敏感类场景,也只适合在单体应用进程内调用。
最后,一组抽象良好、边界清晰、颗粒度合适的微服务,其实是非常考验架构师的经验和功力的。虽然现在微服务架构已经几乎成为互联网架构的默认风格,但门槛其实没有很多人想得这么低。
无服务器架构风格的实践建议
那么针对AWS Lambda和无服务器架构风格有什么实践建议吗? 我这里总结了几条:
首先是尽量使用无状态函数
从设计的角度看,无状态函数更容易推理和理解,从计算资源的角度看,无状态函数可以实现执行完即释放。在无服务器架构里,我们应该尽可能使用函数式变成风格,将Lambda设计为可以独立和异步执行的简单和无状态任务。
其次是尽量使用事件驱动
在主动调用编排,和响应式的事件驱动风格之间,我们总是更推荐你使用事件驱动的通信风格。这样也可以降低架构的复杂性,避免出现Lambda pinball。
然后是分层与解耦
即使使用Lambda和无服务器架构,你也是可以考虑适当进行逻辑上的架构分层,尽可能把代码以类库或外部服务的方式进行封装复用。
AWS Lambda跟其他云服务相比,我们建议更加小心优化配置。选择正确的内存大小、运行时间和配置选项都可以帮助优化函数性能和冷启动时间,达到最终节省成本。
再就是对分布式系统常见的几个建议
为失败设计,为你的AWS Lambda功能失败和异常实施适当的错误处理和重试逻辑。
度量与监控, 用日志记录、跟踪和度量工具来监控和调试Lambda功能和性能。
以及自动化一切,尽可能用使用支持无服务器开发工作流程的自动化工具和框架测试和部署功能,减少手工干预错误。
最后还是回到软件开发行业的那句老话,没有银弹。无服务器函数并不是解决所有问题的灵丹妙药。在采用它们之前需要考虑它们的局限性和权衡取舍。
为了避免 Lambda 陷阱,Thoughtworks 建议重新考虑无服务器方法对手头问题的适用性。我们建议,无服务器功能最适合简单、无状态和短期任务,这些任务可以从云的可扩展性和成本效益中受益。对于需要状态管理、数据一致性或事务完整性的更复杂或长时间运行的任务,我们建议使用其他架构或技术。
最终,这是一个架构选择问题,而你也需要为自己的选择买单。