3.1.1 状态与有状态函数
维基百科将计算机科学中的有状态系统定义为:需要记住之前的事件或用户交互的系统。由于计算过程最关注的往往是被循环使用的数据,所以狭义的状态可以定义为,在计算过程中被循环使用的数据。例如,机器学习中模型训练的大部分算法的设计思想是在构建合适的损失函数后,利用链式法则进行求导以计算出“梯度”,随后使用递归方式循环更新已有的模型参数。这些模型参数是训练过程中需要被循环使用的数据,即一种状态。
从用户角度看,状态具有若干属性,如状态的生产者、消费者、类型、格式、数量、大小、访问频度等,表3-1列出了状态的常见属性。
表3-1 状态的常见属性
近年来,随着机器学习、大数据和各种在线应用的迅猛发展,系统状态的数量、大小和访问频度都有较高提升,用户更加需要低时延、高性能、高可靠的状态管理。
• 状态数量的增加:机器学习中卷积神经网络的参数个数与计算层数相关,层数越多,参数个数也越多。随着深度学习的发展,大规模分布式机器学习中的参数可达到十亿至万亿个,对于参数加载的速度有较高的要求。
• 状态大小的增加:大数据计算过程中会产生大量的临时数据,这些数据可能会达到GB甚至TB级别,在各个计算任务之间共享这些数据时需要低时延的加载机制。
• 状态访问频度的增加:大型多人在线游戏的在线用户数可达到百万级别,服务器需要与这些用户产生每秒百万甚至千万次的实时交互,这对于游戏状态的刷新速度有较高要求,延迟大于100ms就会影响用户体验。
3.1.1.1 有状态函数的特征
通常用y=f(x)来描述一个纯函数,即对于确定的输入x,函数f会有确定的输出y。但是,实际上很多系统的输出会受到自身状态的影响,可以这样描述:〈y,Sn+1〉=f(x,Sn),即对于函数f,函数的输出不仅受到输入x的影响,还受到当前状态Sn的影响,而且状态也会在输入x的作用下改变为Sn+1。
在处理状态时,现有的Serverless平台将状态S的变化交给开发者自己处理。例如,开发者需要将状态保存到外部存储上,使用时再从外部存储读取,因此需要自己在读写状态时进行“锁保护”操作。与此不同,一个有状态函数应用的状态S由系统平台管理。
• 程序状态由系统管理:状态管理的操作包括状态的生成、状态的访问、状态的操作、状态的删除及状态的回收。华为元戎提供本地内存方式访问程序状态的能力,在降低开发者应用开发难度的同时保证状态访问的高效性和原子性。
• 状态是编程模型的核心:函数可以访问应用所定义的状态,这里的访问是静态关系。函数运行时会产生函数实例,每个函数实例同一时刻只允许操作一个状态。华为元戎系统调度的对象包括状态及处理状态的函数实例,而且能够以状态为中心进行近状态的函数调度以提高性能。对于一个无状态的应用,可以将其看作是状态为空的应用,因此函数处理的是“空状态”。
• 函数是处理状态的接口实现:开发者需要定义状态及操作状态的接口,操作状态的接口实现就是函数。华为元戎系统未限定接口函数的数量,因此一个状态可以被多个函数操作,接口函数对状态的操作由华为元戎系统协调调度,确保对同一个状态操作的“原子性”。
3.1.1.2 有状态函数的优势
现有的Serverless平台大多要求用户编写无状态函数。无状态的核心特征是,将计算和状态存储分离,因此无状态的函数实例实际上是无差异性的,可以任意增加或减少函数实例,便于进行水平扩展和故障恢复,但同时也给应用开发带来了一系列问题。
• 外部依赖性:如果需要存储状态,无状态函数需要依赖外部的有状态存储服务来实现,既增加了性能开销,又增加了额外成本。
• 网络开销增加:与外部存储的交互增加了网络开销,导致响应变慢。如果对响应速度敏感,则需要引入缓存机制,但使用缓存时需要考虑缓存更新和失效机制,而且不同节点上的缓存内容也需要保持一致(因为每次请求不会落在同一个函数实例上),这既增加了复杂度,又额外占用了资源。
• 可用性要求高:外部存储需要实现高可用,特别是数据量变大时,分片、备份、恢复机制都需要做好。
• 编程复杂:针对每一种外部存储,开发者都需要设计一些client或repository类来封装对状态进行读写的代码。对于高并发场景,开发者还需要考虑“锁保机制”的设计,避免出现并发性错误。
上述这些问题会限制Serverless的应用范围,特别是对于以数据为中心的应用和对时延敏感的应用。
为了克服这些问题,有状态函数在原生设计上将状态和函数实例紧密关联,状态内置于系统管理之中,函数实例不再是无差别的,而是有差异的,比如某一个用户的请求总是会落在同一个函数实例上。这样带来的优势包括以下几个方面。
• 更易于理解和实现编程模型:用户通常只需要对函数中的一个简单结构体进行操作,存取状态数据更容易。Serverless平台接管了状态管理,因此可以为用户提供多种数据一致性模型及并发场景下锁的处理机制,从而使编程模型更加容易理解且函数代码更加简洁。
• 数据本地化:由于不需要频繁和外部存储服务进行交互,减少了网络访问的次数,从而能够减少时延。这样,对于经常操作状态或有大量状态数据的应用就能够获得更快的响应。
• 更高的可用性:由于数据不需要分发到外部存储中,应用的可用性只依赖函数系统;系统提供的多种一致性模型和并发处理机制也可以提升应用的可用性。