无服务器场景(serverless)的容错怎么做?我们的设计
无服务器架构和FaaS(Function-as-a-service 函数即服务)近年来越来越受欢迎,这要归功于易用性、自动伸缩和按需付费的特性。然而,FaaS 基础设施中缺少应用程序的容错能力。
把坑留给码农?
默认情况下,AWS Lambda 或 Google Cloud Functions 等 FaaS 系统需要开发者自己考虑执行失败的情况。云服务的策略是:无论是应用程序自身错误还是基础设施故障,一旦函数执行失败就执行重试。这意味着函数可能会运行多次。
并且更可怕还有,你的业务代码也可能会运行 0.5 次,或者 3.2 次……这是怎么回事?
这个糟糕问题的原因是,大多数 FaaS 系统提供的能力不能保证资源层(如数据库或文件等)在执行失败后被清理。
在失败和重试的过程中,修改共享状态的应用程序会在不知不觉修改部分结果。如果一个请求将更新两个键 k 和 l,但函数在两次更新之间崩溃,那么现在客户端将看到较新版本的 k 和较旧版本的 l。
这就是云平台 FaaS 的现状。
FaaS 的无痛容错:原子性
为了避免这种类型的异常,开发人员需要一个简单的保证,那就是原子性:要么一个请求的所有操作全部成功,要么全部失败。传统做法中,原子性是由强一致性(事务式)存储引擎来保证的,但这些系统有众所周知的扩展和性能问题。如何才能让 FaaS 执行保证原子性?
为此,我们构建了一个名为 AFT(Atomicity for Fault Tolerance)的系统,它是一个位于任何无服务器计算层(如AWS Lambda、Google Cloud Functions)和存储层(如AWS DynamoDB、Redis)之间的胶合层。计算层的每个逻辑请求(可能由多个函数组成)都被视为一个事务。AFT 保证事务所做的所有更新都会在存储层原子化执行。
AFT 的设计是灵活的。我们对计算层不做任何假设,而对存储层的要求就是存储层要持久化。我们可以保证在 DynamoDB 和 S3 等最终一致的系统上运行的函数的原子性。AFT 有两个主要特点。(1)无协调、原子更新;(2)保证事务只读取已提交的数据。AFT 将每个新的key版本写到不同的物理存储位置,以避免写-写冲突。
为了保证事务读取语义上一致的数据,AFT 保证了读取原子性。读取原子性要求客户机只从已提交的事务中读取数据,并按照事务提交的顺序进行读取。也就是说,如果事务 T1 写了 key 的版本 K1,而后来的事务 T2 写了 K2 和 L2,那么客户端就读不到 K1 和 L2,因为事务 T2 写了一个较新版本的K,称为 K2。所有这些都可以在没有任何协调¹的情况下完成。这种类型的异常被称为断裂读。
在此之下,我们开发了新的协议,以确保写入是原子化更新的,同时也保证了读取的原子性。我们还开发了新的垃圾收集协议,以减轻存储系统版本化的开销。如果你有兴趣了解更多,请点击这里查看完整的论文 [1]。
我们在三个存储后端(AWS DynamoDB、AWS S3和Redis)上,用几千行 Go 代码实现了AFT及其协议。我们能够最大限度地减少(相对于直接从底层存储引擎读写)IO 开销(具体结果见论文第 6 节),AFT 可以平滑地扩展到数百个并行客户端和每秒数千个事务(见下图)的规模。同时,我们避免了没有 AFT 的情况下出现的大量异常现象。下表显示了在 10000 个事务中,读-写(事务内)和断裂读异常的频率。
下一步
我们对胶合层架构作为探索不同开发者保障的手段感到兴奋。特别是有一整类应用可以从无服务器架构中受益,但需要找到将更强的一致性引入 Faas 世界的方法。
熟悉数据库内部的朋友可能已经注意到,我们这里讲到的读取原子性保证类似于无协调版本的快照隔离,这是传统数据库中常用的强一致性形式。我们正计划在此背景下探索如何将强一致性引入到无服务器应用中。如果你对这些有兴趣可以通过原文链接反馈给我们。
原文地址:
https://medium.com/riselab/solving-serverless-computings-fault-tolerance-problem-122128fa5787
完整论文链接:
[1] https://arxiv.org/pdf/2003.06007.pdf
参考阅读:
本文由高可用架构翻译。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。
高可用架构
改变互联网的构建方式
长按二维码 关注「高可用架构」公众号