从 MVC 到 FaaS —— 如何开发企业级 FaaS 应用

发布于 2019-04-22

这是我在腾讯云 Hello, Serverless 沙龙的演讲内容,转自简单心理技术团队博客。

因为是演讲稿的形式且信息量较大,可能不太适合阅读,之后我会分主题细化介绍各个概念和理念。

如有疑问欢迎留言。

大家好,我是来自简单心理的朱峰。很高兴在这里与大家分享简单心理在 Serverless 方面的一些实践经验。

相信大家今天来到这里,是对 Serverless 抱着期待和困惑来的,特别是对于企业级的开发,可能大多数人是困惑会更多一些。

简单心理是从去年第三季度开始尝试 Serverless 的,今年正式全面转向 Serverless 架构,用了大约半年的时间,摸索出一整套企业级 Serverless 应用框架。随着这套框架的诞生和成熟,我们发现了越来越多 Serverless 有别于单体应用或微服务的特性。这些特性让我们意识到 Serverless 虽然不是银弹,但确实可以有效提升开发效率,降低后续的开发和维护成本。

今天的演讲内容主要分三部分:

什么是 FaaS?为什么使用 FaaS?如何使用 FaaS?

今天前面的几场演讲已经介绍过了从硬件服务器到虚拟化,再到 Serverless 的进程,所以我就不再介绍这个过程了。

但 Serverless 其实按照托管的对象来区分,有两个子集。一个是函数托管服务,也就是 FaaS(Function as a Service)。一个是应用托管服务,通常被称为 PaaS(Platform as a Service)。

FaaS 是专指 Serverless 服务中的函数服务,开发者只需上传函数,剩下由服务商来负责按需执行和横向扩容等事情。

目前大多数云服务商提供的 Serverless 服务都是 FaaS 服务,比如亚马逊的 Lambda、腾讯云的云函数等等。

PaaS 主要的服务商是 Google 的 App Engine。

了解了 FaaS 的概念后,我们再来看看为什么要选用 FaaS?比起单体应用、微服务架构等,FaaS 架构的优势究竟在哪里呢?

我从三个层面进行了总结:

首先是开发层面。

就学习成本来说,由于 FaaS 的基本单位是函数,且函数本身是很纯粹的面向过程的编码方式,非常贴近人们的日常表达方式,所以无论是技术角度还是沟通角度来说,其学习和开发的成本都较低。

在多人协作时,通常不太可能出现多人同时修改同一个函数的情况,所以几乎不会出现代码冲突,非常便于多人协作开发。

最后,从技术债的角度来说,无论是单体应用还是微服务,都无法避开业务快速增长时所带来的大量难以处理的技术债问题。FaaS 也无法避免技术债的产生,但由于函数这个颗粒度足够小,因此我们可以非常方便的找到重构的切入点,进行绿色环保施工,轻松进行各种程度的重构工作,使得技术债的偿还成本被极大的降低了。

这也是 FaaS 中最吸引我的优势。野蛮生长是每个公司都会经历的阶段,技术债也是每个开发者的痛,由于 MVC 框架通常会牵一发而动全身,如何恰当的选择重构的切入点和控制影响范围不仅需要技术,更需要经验。相比之下,FaaS 的切入点非常自由和灵活,在有良好框架和工具链的支持下,影响范围将可以缩小到函数级。

对于充满不确定性的新业务来说,FaaS 将是一个非常易于上手,且不用过于担心技术债的架构。

在运维层面,除了服务商全权提供执行、扩容、安全和灾备等服务外,因为云函数本身具有独立执行的特性,所以发生单点故障时,其受影响的范围也比较可控。

成本方面的特性简单来说就是便宜,因为按量计费,不需要购买服务器和带宽等。高峰时也会自动伸缩,但目前因为冷启动的原因导致性能爬坡比较慢,不过如果使用的是腾讯云的话可以联系客服申请提前预热实例来一定程度上避免性能爬坡的问题。

目前 FaaS 在服务商层面其实已经满足了基本可用的需求了。但在企业级开发的过程中,由于缺乏框架和最佳实践,导致难以落地。所以今天我会重点分享一下简单心理在 FaaS 的具体实践经验。

这一块分三部分来讲,首先是知识储备,然后是框架,最后是命令行。

FaaS 本身非常容易上手,因此在知识储备层面,我也将其控制在一个掌握基本编程知识的初级开发者即可上手的范围内。

首先是与 FaaS 无关的知识:编程语言的基本用法和业务相关知识。

业务相关知识就不说了,说一下编程语言的基本用法。

因为简单心理用的是 Node.js,我就以 Node.js 举例,这里的基本用法是指最常用的那些知识,比如常用类型及其方法,需要掌握如:字符串如何拼接、替换,当前时间戳如何获取等等。

但这里特别注明不需要掌握 MVC 框架的知识。最多根据实际业务情况,可能还需要掌握一些内置库(比如读写本地文件)和特定情况需要用到的第三方库(比如 mysql、redis 等等)。

接下来是 FaaS 的基础知识。主要是两个概念:云函数和触发事件。云函数就是 FaaS 的基本单位,是一个负责接收和处理事件的函数。对于触发事件,不同业务类型可能有不同的侧重点,简单心理主要使用网络请求和定时触发这两种事件。如果是以数据处理为核心业务的公司,可能消息队列相关的事件用的更多一些,这里我就不展开了。

了解了 FaaS 的基础知识之后,接下来是云函数框架所需的基础知识。

先说明一下,这个框架目前仅在简单心理内部使用,上手这套框架,需要了解三个抽象概念:命名规则、模板和插件。

我先粗略介绍一下这三个概念,接下来会更详细的介绍每一个概念。

首先是命名规则,命名规则的作用是管理大量的云资源的,这个云资源包括云函数、云消息队列、云网关接口等等。通过命名规则,将这些云服务融为一体进行开发和使用。

然后是模板。为了简化开发工作和提升开发效率,我们将一些常用业务流程进行抽象,并制作成模板,开发者可以通过模板来生成业务流程所需的云资源。

插件是对常用技术的封装,比如数据库、第三方接口等等,当然我们也支持直接使用第三方 npm 包。

按照一个云函数只处理一个业务流程的最佳实践来操作时,我们首先遇到的问题就是一个复杂的企业级应用,必定包含有很多业务流程,如何管理这些云函数呢?除此之外,FaaS 作为云原生架构,必定还会跟各种其它云服务交互,如何交互以及管理这些云服务呢?

为了解决这两个问题,我们设计了一套命名规则,涵盖了云函数、消息队列和网关接口,把这些常用的云资源融为一体。

云函数的命名规则为 <环境><类型>--<命名空间>--<资源>--<行为>

消息队列的命名规则为 <环境>--<命名空间>--<资源>--<行为>,消息队列不包含类型是因为消息队列会在这个行为中共享,具体细节等下讲模板的时候会讲到。

网关接口的命名规则为 /<命名空间>/<资源>/<行为>,因为不同的环境使用不同的网关的,所以网关的命名规则里不包含环境。

接下来说下这些变量的含义:

首先是环境,我们区分了两个环境,正式环境和测试环境。

接下来是类型,我们按照云函数的触发情况,对云函数进行了分类。大类分两种:执行函数和触发函数。触发函数就是被触发条件唤起的云函数。

我们为了便于共用,触发函数通常并不执行业务逻辑,而是通过腾讯云的接口来调用执行函数,由执行函数来执行业务逻辑。

调度函数和网关函数都属于触发函数。调度函数一般是由定时触发器触发的,网关函数是由网络请求触发的。另外简单心理有多种网关,所以实际上网关函数我们还区分了多个类型,但是我们公司业务的特定情况,所以这里就不展开了。

另外,对于网关类云函数,我们会自动创建对应的网关接口,请求路径会按上面网关接口的规则来生成。调度函数则会有对应的消息队列,消息队列的命名规则也是如前面所说的来生成。

命名空间、资源、行为,这基本上就是按 Restful 的风格来设计的,命名空间是为了增加一层命名层级,方便管理复杂的应用。

因为有命令行的存在,所以其实命名规则知道就可以,如果输入的函数名不符合规则,命令行会有提示。我们实际进行开发前,会先商量好要新建的云函数的名称,然后再开始开发。

接下来我们来说模板。

由于企业级应用中,一个云函数通常并不足以完成一个较复杂的业务流程,各种不同的使用场景,需要多个云函数协同完成,但一个个创建云函数会非常繁琐,因此我们设计了模板机制来优化开发体验。

在介绍模板的细节前,让我们先从一个新手的角度,了解如何快速找到与业务流程一致的模板。

我们设计了四个问题来快速定位适合的模板。问题如下:

首先是否由网关触发?为了更易于理解,也可以把网关触发视为由用户行为触发。因为一般来说大多数业务流程都是由用户行为发起的,所以如果这里选择了是,那最终的推荐模板里第一个云函数将是一个网关函数。

第二个问题是:是否需要立即执行?我们内部约定除非是纯读取数据的业务流程,否则尽量不立即执行。表面上这会导致性能降低,但长期来看会使得后续功能扩展更便捷。而且因为有现成的模板,不立即执行也并不会导致更大的开发工作量。

第三个问题是:是否可以异步执行?对于非立即执行的业务流程,可以选择同步执行或异步执行。同步执行是指类似于计算账户余额这种事务性操作的情况。

同时这里也特别说明一下,为了简化开发逻辑,我们是禁止使用分布式事务的。对于单一的事务操作,通过同步执行流程来完成。多个操作组合的事务,我们采用的方法是串起多个同步执行流程。对于其中会产生的性能问题,可以视情况增加一个分配的步骤。比如计算余额这个事情,因为余额对于不同账户其实是分开计算的,所以可以通过账户ID的尾号数字0到9,分配为十个同步处理流程。这里我再提一下,这么做一方面是保证代码和业务流程清晰易懂,另外一方面也是保证初级程序员也可以掌握这种模式。

最后一个问题是对于异步执行,是否需要控制并发量?大家还记得上一页提到的调度函数吗?调度函数有两种用途,一种是同步执行,一种是控制异步执行的并发量。为什么要控制并发量呢?比较典型的原因就是错峰平谷。比如一些对数据库负荷比较高的操作,可以通过控制并发量避免数据库满负荷运行。或者大促活动期间,可以临时调低甚至暂停部分非核心业务流程,保证核心业务的稳定运行,待高峰期过去后再处理积压的事件。

现在再整体回顾一下这四个问题:

是否由网关触发,将决定是否需要一个网关函数。是否立即执行决定是否仅仅一个网关函数或一个其它类型的触发函数就够了。同步执行或者控制并发量的异步执行,需要有一个调度函数。对于非立即执行的情况,都需要一个执行函数。

新手可以通过回答这四个问题,得到一个明确的模板,这个模板里会包含1-3个云函数。

新手了解到要用哪个模板时,其实就已经可以开始开发了。不过在场的各位应该不会仅满足于此,所以我再深入讲一下模板。

先说模板类型。

模板分两大类:通用模板和专用模板。前面讲到的如何选择模板,最终给出的模板就是通用模板中基础模板和组合模板。

每种函数都有一个对应的基础模板,这些基础模板针对性的优化了该类型函数的开发体验。等下会展示一些具体的模板,现在先不细说。

组合模板就是指一个模板里包含了多个其它模板,会生成多个云函数。

而扩展模板则是一些特定用途的模板,或者是组合模板的组成部分。

同时,为了便于开发,我们也设计了专用模板,用于在各个命名空间和资源下开发特定的模板而不会干扰到通用模板。

比起模板,插件简单很多,只区分通用插件和专用插件。通用插件比如数据库请求的封装,网关请求的封装等。

插件可以独立使用、互相引用或者被模板引用。所以部分跨模板的功能封装,也会被拆分成插件的形式。

我们主要通过模板和插件的组合,来大幅简化日常的开发工作。

命令行是用于快捷处理云函数整个生命周期的工具。

我简单介绍一下这几个命令行的作用。

创建云函数是用于基于模板来创建云函数的指令。

添加插件指令,是用于后期给指定云函数添加插件的。

更新云函数的模板和插件,是用于把已有的云函数的模板和插件更新到最新版本,一般是当某个云函数需要后续开发维护时使用。

测试云函数指令是我们基于 Node.js 的测试框架 Jest 封装的,支持三种测试模式:本地测试环境测试、远程测试环境测试和远程正式环境测试。

所谓远程测试,是指直接调用腾讯云的接口,去触发云函数来验证返回结果是否正确,如果是网关类型的云函数,则会直接请求网关接口来进行测试。

发布云函数指令,除了发布和更新云函数,还能同时发布和更新对应的网关接口和消息队列。

两个批量功能,是因为实际开发时,一个新功能通常会有多个业务流程,批量测试和发布可以少打不少命令行。

FaaS 本身既然提供了如此良好的底层,那框架层面何时可以完善呢?答案不是再等几年,而是今年 😛

我正在基于简单心理的 FaaS 实践经验,开发一套全新的 FaaS 框架,这个框架将基于 Node.js,使用 TypeScript 来开发。

一体式开发环境:单体应用的开发体验 + 从本地开发到线上故障排查完整流程管理。

这个主要是解决两个痛点:一是琐碎的云函数的开发体验较差,二是线上故障遇到牵涉到多个云函数时,追查起来复杂。

渐进式开发体验:从 Hello world 到复杂的企业业务拆解;快速开发的同时维持较低的技术债和极低的重构成本。

这个主要是为了一开始可以用比较简单的方式构建项目,后期则可以用一些辅助工具来重新整理命名这些初期的云函数,使得项目可以健康成长。

灵活的插件机制:从公有云服务插件到企业定制私有云服务;从通用业务插件到企业定制业务插件。

新框架中会以插件的形式提供各种功能,模板机制也会被并入插件机制中。

为多人协作、多团队协作和云端开发而生。多人协作的特性是 FaaS 自带的,就不多说了。我个人认为云端开发是未来的趋势,就像我刚刚用 Coding Studio 演示一样,新人只需注册一个账号,分配一下权限,就可以立马上手了,不再需要配置本地环境,也跟电脑的操作系统无关,甚至是不是用电脑都无所谓了。