1.4.4 函数工作流
本节将介绍典型商用平台的函数工作流Lambda、Azure Durable Function及OpenWhisk Composer。
工作流(Workflow)是对工作流程及其各操作步骤之间业务规则的抽象及概括描述。在微服务架构下,通常会使用基于BPMN或更轻量化的工作流引擎来编排微服务,完成对业务流程的执行和监控等工作。当承载业务的单元从微服务变为函数时,同样需要这样的工作流引擎来编排函数,完成如下操作。
(1)顺序调用函数。
(2)并行执行函数。
(3)分支执行函数。
(4)失败重试,或者应用try/catch/finally。
函数工作流(也称函数编排)当前有两种形式:一种是以Step Function为代表的基于Markup标记语言(JSON)的工作流,与其相同的还有CNCF开源的Serverless Workflow规范(详见Serverless Workflow官网);另一种是以Azure Durable Function、OpenWhisk Composer为代表的基于编码实现的工作流形式。
1.4.4.1 基于JSON的函数工作流Step Function
函数的执行时间有上限,但是工作流引擎可以长时间运行任务,并保证执行过程的高可用,以及不丢失状态数据。商用平台如AWS提供了Step Function服务,支持在复杂业务场景下对函数及其他AWS服务进行编排。Step Function提供了可视化和基于配置文件(JSON)两种编排方式。Step Function的执行过程用一个状态机来实现,每个节点有7种不同的状态类型,如表1-2所示。
表1-2 Step Function的状态类型
工作流上的所有任务都是通过Task完成的,Task可以是一个Lambda函数,也可以是Activity(可长时间运行的任务)。
图1-28 并行执行OCR识别的Step Function工作流视图
图1-28是一个并行执行后聚合(fork/join)的示例[4],旨在将图片分给三个OCR提供者来并行识别,以获取最可信的结果。并行state的output会将每个分支的输出组合起来。下面是其实现的JSON代码片段。
1.4.4.2 基于事件溯源编排的Azure Durable Function
Azure Durable Function的特点是通过函数编码的方式实现了对函数工作流的支持。Durable Function包含三种函数,即编排(Orchestrator)函数、任务(Activity)函数和实体(Entity)函数,其中任务函数是承载业务逻辑的函数。
如图1-29所示,Durable Function通过事件溯源的机制来保存编排函数的局部变量的状态,从而保证事件重入后可以从记录点继续执行。它需要外部存储服务来记录工作流的历史状态及实例的状态,配合完成整个编排过程。
图1-29 Durable Function的编排依赖数据库及消息队列
Azure将常用的工作流总结为6种模式,而Durable Function完全支持这6种模式。
• 链式调用(函数顺序调用):适用于需要顺序工作流的场景,按顺序去调用多个函数来完成完整的业务逻辑。
• 扇出/扇入(Fan-out/Fan-in):适用于并发执行多个函数后进行聚合的场景。
• 异步HTTP API:适用于外部客户端查询长时间运行的工作流运行状态的场景。
• 监视:适用于在工作流的执行过程中,需要持续轮询是否达到条件,然后执行下一步的场景。
• 人机交互:适用于工作流中需要人工干预的场景,比如人工审批。
• 聚合器:适用于需要将一个时间段内的多个事件聚合成单个实体数据的场景。
下面的代码是Azure Durable Function官方文档的一个函数链式调用的示例,编排函数顺序调用F1~F4这4个函数,每一个输出结果作为下一次调用的输入。F1~F4是普通的Azure函数,在Durable Function中被视为Activity函数,承载具体的业务。Context.df可以按名称调用函数,默认为异步调用,yield关键字则可以记录检查点。F1被调用后,编排函数退出,等到F1返回结果时,编排函数重入,获得最后一个检查点的结果,然后执行下一条代码,以此类推。同样,如果编排函数在执行过程中意外退出,函数实例重入时将从上一个yield调用处继续执行。
Durable Function对编排函数的实现存在一些约束,要求代码最好是幂等的,所以在编排函数的操作中要注意以下事项。
• 代码不能依赖随机数、当前的时间等。
• 不在编排函数中做I/O或自定义的线程调度。
• 不要在代码中写无限循环。
Durable Function在设计上对数据的持久性(Durability)更关注,不太适合关注性能的工作流,但其在数据访问和函数编排上有自己的特色。
1.4.4.3 基于函数编排的OpenWhisk Composer
和Azure Durable Function类似,OpenWhisk自身支持Conductor Action的函数类型,这种函数可以完成函数编排,支持分支、顺序等编排方式。如果Conductor函数在执行过程中调用了其他函数,那么该Conductor函数可以退出执行。例如,下面的Composer文档中样例Conductor函数代码中的triple函数被调用并执行后,OpenWhisk系统将重新触发Conductor函数,Conductor函数根据此时的状态触发下一个函数,直至Conductor函数的代码全部执行完。和Durable Function使用事件溯源、依赖外部存储服务的方式不同,OpenWhisk基本不依赖外部存储服务。
基于Conductor函数,OpenWhisk抽象了一套编排的DSL接口,将其命名为Composer,用来支持函数编排中常用的顺序、分支、fork/join等。在OpenWhisk系统中引入额外的Redis组件后,也可以支持长时间运行的编排任务。
下面这段Composer的编排代码完成一个分支任务,如果通过身份验证,则返回成功,否则返回失败。这个编排函数在部署时,会被编译并视为一个Conductor函数部署到OpenWhisk。
Composer的这种编排方式对开发者来说,学习成本很低;而对平台的维护者来说,基于同一个系统支持函数及编排,也节省了维护的成本。
两种工作流方式各有其适合的使用场景。Step Function类的编码方式可以方便地进行可视化,对业务人员更友好。Durable Function类的编码方式对开发者更友好。开发者可以根据实际的情况使用不同类型的工作流服务。
综上所述,本节从平台、框架、事件总线及函数工作流等维度介绍了典型的Serverless平台及其生态发展。目前Serverless平台虽然百花齐放,但距离成熟、标准化还有一定的差距,这也意味着Serverless方向有很多的创新机会,希望广大开发人员抓住机遇、共同努力,投入新一代Serverless生态发展和产业建设的大潮之中。