社区里关于最佳实践的讨论已经持续了很多年。其中有一些方式,评价一直都非常不错。
遵循这些惯例的大部分 serverless 从业者都有大规模开发的经验。Serverless 主要特性体现在在大规模和突发性环境里,而不是相对较低的环境。因此,这些最佳实践是从大体量触发的。例如零售领域的 Nordstrom,物联网领域的 iRobot。如果你的体量还不是这么庞大,就没必要遵循这些。
请记住,最佳实践不是唯一的真理。最佳做法取决于一系列基本假设。如果这些假设不符合你的场景,那么这些最佳做法可能不适合。
我的主要假设是每个人在构建自己的应用程序,都考虑能够大规模运行(即使它永远不会最终大规模运行)。
下面内容是我个人总结的 serverless 最佳实践。
每个函数只做一件事
这样做的目的是缩小规模,方便定位错误。
换句话说,在函数里面使用了 switch 语句,就不是一个合适的做法。
许多教程和框架都基于单个代理路由的单体应用并使用 switch 语句来工作。我不喜欢这种模式。它无法很好地扩展,并且往往会让函数变得庞大而且复杂。
一个/几个功能运行整个应用程序的问题是,在扩展时最终会扩展整个应用程序,而不是扩展特定元素。
如果你的 Web 应用程序中有一部分会被调用一百万次,而另一部分调用次数为一千次,那么你必须针对百万计调用优化你的函数,同时包含调用仅上千次的代码。那是浪费。分开他们。这有很多价值。
函数不相互调用
函数之间相互调用是反模式的。
在极少数情况下,这是有效的,但这种模式不易分解。
简单来说,不要这样做,你付出了双倍的开销,让 debug 变得更加复杂,而且还减少了函数的独立性。
函数应该把数据推送到数据仓库或者消息队列中,由它们按照需求触发其他函数。
在函数中尽可能少使用库(最好为零)
这点的好处显而易见。
函数有冷启动(首次调用)和热启动(已被调用,且实例未被销毁)。冷启动受很多因素影响,包括函数文件的大小,已经需要被实例化的库数目。
包含的代码越多,需要实例化的库越多,冷启动速度就越慢。
例如,在某些平台上,Java 是一种出色的高性能语言。 但是,如果使用许多库,就会发现冷启动需要花费很多秒的时间。 几乎肯定你不需要它们,冷启动性能不仅会影响启动,还会影响扩展。
还有一点,我坚信开发人员仅在必要时才使用库,除非是一个脱离了就无法构建的库。
诸如 express 之类的东西是为服务器构建的,无服务器应用程序不需要当中的所有元素。那么为什么要引入所有代码和依赖关系呢?为什么要引入多余的代码?这不仅仅是永远不会运行的事情,而且可能带来安全风险。
这种操作方式非让让人信服。 当然,如果有一个经过测试,了解和信任的库,那么引入它完全没问题,但是其中的关键要素是测试,了解和信任代码。 这和简单看一个教程,是不一样的。
避免使用基于连接的服务,例如 RDBMS
除非必要,就不要用。
很多 web 开发者都会跳进一个坑,“RDBMS 这玩意我熟悉啊”。
但这和 RDBMS 无关,问题的关键是连接。
Serverless 最好工作在服务上而不是连接。
服务旨在快速的对请求做出请求,并且处理隐藏在服务层下数据层的复杂性。这在 serverless 领域具有重大价值。这也是为什么像 DynamoDB 之类的应用非常契合 serverless 范式的原因。
其实,serverless 并不拒绝 RDBMS,拒绝的是连接。连接存在开销,如果你想扩大一个函数的规模,且每一个函数环境都需要一个连接。那么就必然会无休止的导致冷启动的瓶颈和 I/O 等待。
最重要的是,serverless 架构需要你重新思考你的数据层。这不是 serveless 的问题。如果你尝试复用现在数据层,但是发现并没有用。那可能是缺乏对 serverless 架构的理解。
一个路由对应一个功能(HTTP 调用)
如果可能的话,避免使用单函数路由。不易扩展而且不能解决隔离问题。在某些情况下,你可使用。比如,一组函数和一张表严格绑定在一起,并且和其他程序完全无关。但是在我工作的情况里,属于极端情况。
这在管理方面增加了复杂性,但是在应用程序扩展时,确实有助于隔离错误和问题。按照你的意愿进行程序。
但是,无论如何,你还是需要配置管理工具来运行所有内容? 或者你已经使用了某种 CI/CD 工具?那么你仍然必须在 serverless 上践行 DevOps。
学习使用消息队列(异步大法好)
severless 只有在程序是异步的才能更好的工作。这对于习惯请求-响应式和大量查询的程序员来说不是很直观。
回到不调用其他函数的函数,必须指出这是将函数链接在一起的方式。 队列在链接方案中充当断路器,因此,如果某个函数失败,则可以轻松耗尽由于故障而备份的队列,也可以将失败的消息推送到死信队列。
回到程序之间不互相调用上。使用消息队列是讲函数链接起来的方式。队列在链接函数中起到断路器的作用。因此,如果某个函数失败,则可以轻松耗尽由于故障而备份的队列,也可以将失败的消息推送到死信队列。
简单来说,学号分布式系统如果工作很重要。
对于具有无服务器后端的客户端应用程序,最好的方法是使用 CQRS。将检索数据的点与输入数据的点分开是这种模式的关键。
数据流,而不是数据湖
在 serverless 系统里面,你的数据在系统中流动。最终可能会保存在数据湖中,但是在系统中处于流动状态。所以在任何节点都应该把数据当作流动的,而不是静止的。
并非总是可行的,但请尝试避免在 serverless 环境中从数据湖查询。
serverless 需要重新考虑数据层。这是最大的障碍,因为 severless 的新人们倾向于使用 RDBMS 并陷入僵局,这不仅是因为它们跟不上规模,而且他们的数据结构变得过于僵化导致快速不起来。
你会发现,随着应用程序的更改和扩展规模的变化,数据流也会发生变化。如果你要做的就是重定向,那很容易。构建数据湖则要困难很多。
我知道这一点比其他地方要“实际”一点,但这并不是直接就能实现的。
只考虑扩展是错了,你需要考虑如何扩展
创建第一个无服务器应用程序并观察其如何扩展非常容易。但如果你不了解自己所做的事情,则很容易陷入使用其他所有自动缩放解决方案的陷阱。
如果你不考虑自己的应用程序以及如何扩展应用程序,那么你就会遇到麻烦。如果以缓慢的冷启动(例如,大量的库和使用 RDBMS)进行操作,之后使用量激增,会显着增加函数的并发数,然后使连接数达到最大值,最终降低应用程序的速度。
因此,不要只是将应用程序放在 serverless,就像当然它在高负载下仍能正常工作。了解高负载下的应用程序仍然是需要解决的问题。
结论
我可以写更多东西,但是上面这些是我在和人交谈时最需要向它们解释的事情和观点。
我没有提到诸如如何规划应用程序或如何考虑使应用程序成本上升之类的事情,这些超出了本人的讨论范围。
欢迎大家讨论。可以肯定的是,我会收到很多人告诉我我对 RDBMS 的理解是错误的。与容器一样,我不讨厌 RDBMS,但是我喜欢使用正确的工具来完成正确的工作。了解你的工具!