共计 3363 个字符,预计需要花费 9 分钟才能阅读完成。
前言
无论是在大数据处理畛域,还是在音讯解决畛域,工作零碎都有一个很要害的能力 – 工作触发去重的保障。这个能力对于一些准确性要求极高的场景中(如金融等)是必不可少的。作为 Serverless 化工作解决平台,Serverless Task 也须要提供这类保障,在用户利用层面及本身零碎外部两个维度具备工作的精确触发语义。本文次要针对音讯解决可靠性这一主题来介绍函数计算外部的一些技术细节,并展现如何在理论利用中应用函数计算所提供的这方面能力来加强工作执行的可靠性。
浅谈工作去重
在探讨异步音讯解决零碎时,音讯解决的根本语义是无奈绕开的话题。在一个异步的音讯解决零碎(工作零碎)中,一条音讯的解决流程简化如下图所示:
图 1
用户下发工作 – 进入队列 – 工作处理单元监听并获取音讯 – 调度到理论 worker 执行
在工作音讯整个的流转过程中,任何组件(环节)可能呈现的宕机等问题会导致音讯的谬误传递。个别的工作零碎会提供至少 3 个层级的音讯解决语义:
●At-Most-Once:保障音讯最多被传递一次。当呈现网络分区、零碎组件宕机时,可能呈现音讯失落;
●At-Least-Once:保障音讯至多被传递一次。消息传递链路反对谬误重试,利用音讯重发机制保障上游肯定收到上游音讯,然而在宕机或者网络分区的场景下,可能导致雷同消息传递屡次。
●Exactly-Once 机制则能够保障音讯准确被传送一次,准确一次并不是意味着在宕机或网络分区的场景下没有重传,而是重传对于接受方的状态不产生任何扭转,与传送一次的后果一样。在理论生产中,往往是依赖重传机制 & 接管方去重(幂等)来做到 Exactly Once。
函数计算可能提供工作散发的 Exactly Once 语义,即无论在何种状况下,反复的工作将被零碎认为是雷同的触发,进而只进行一次的工作散发。
联合图 1,如果要做到工作去重,零碎至多须要提供两个维度的保障:
1、零碎侧保障:任务调度零碎本身的 failover 不影响音讯的传递正确性及唯一性;
2、提供给用户一种机制,能够做到整个业务逻辑的触发去重语义。
上面,咱们将联合简化的 Serverless Task 零碎架构,谈一谈函数计算是如何做到下面的能力的。
函数计算异步工作触发去重的实现
函数计算的工作零碎架构如下图所示
图 2
首先,用户调用函数计算 API 下发一个工作(步骤 1)进入零碎的 API-Server 中,API-Server 进行校验后将音讯传入外部队列(步骤 2.1)。后盾有一个异步模块实时监听外部队列(步骤 2.2),之后调用资源管理模块获取运行时资源(步骤 2.2-2.3)。获取运行时资源后,调度模块将工作数据下发到 VM 级别的客户端中(步骤 3.1),并由客户端将工作转发至理论的用户运行资源(步骤 3.2)。为了做到上文中所提到的两个维度的保障,咱们须要在以下层面进行反对:
1、零碎侧保障:在步骤 2.1 – 3.1 中,任何一个两头过程的 Failover 只能触发一次步骤 3.2 的执行,即只会调度一次用户实例的运行;
2、用户侧利用级别去重能力:可能反对用户屡次重复执行步骤 1,但理论只会触发一次 步骤 3.2 的执行。
零碎侧优雅降级 & Failover 时的工作散发去重保障
当用户的音讯进入函数计算零碎中(即实现步骤 2.1)后,用户的申请将收到 HTTP 状态码 202 的 Response,用户能够认为曾经胜利提交一次工作。从该工作音讯进入 MQ 起,其生命周期便由 Scheduler 保护,所以 Scheduler 的稳定性及 MQ 的稳定性将间接影响零碎 Exactly Once 的实现计划。
在大多数开源音讯零碎中(如 MQ、Kafka)个别都提供音讯多正本存储及惟一生产的语义。函数计算所应用的音讯队列(最底层为 RocketMQ)也是同样的,底层存储的 3 正本实现使得咱们无需关注音讯存储方面的稳定性。除此之外,函数计算所应用的的音讯队列还具备以下个性:
1、生产的唯一性:每一个队列中的每一条音讯当被生产后,会进入“不可见模式”。在此模式下,其余消费者无奈获取该音讯;
2、每条音讯的理论消费者须要实时更新该模式的不可见工夫;当消费者生产实现后,须要显示的删除该音讯。
因而,音讯在队列中的的整个生命周期如下图所示:
图 3
Scheduler 次要负责音讯的解决,其工作次要有以下几个局部组成:
1、依据函数计算负载平衡模块的调度策略,监听本身所负责的队列;
2、当队列中呈现音讯后,拉取音讯,并在内存中维持一个状态:直到音讯生产实现(用户实例返回函数执行后果)前,不断更新音讯的可见工夫,确保音讯不会再次在队列中呈现;
3、当工作执行实现后,显示删除该音讯。
在队列的调度模型方面,函数计算对于普通用户采纳“单队列”的管理模式;即每一个用户的所有异步执行申请由一个独立队列互相隔离,并且由一个 Scheduler 固定负责。这个负载的映射关系由函数计算的负载平衡服务进行治理,如下图所示(咱们在后续文章中还会更为具体的介绍这部分内容):
图 4
当 Scheduler 1 产生宕机或降级时,工作由两种执行状态:
1、如果音讯还未传递到用户的执行实例中(图 2 中的步骤 3.1 ~ 3.2),那么当这台 Scheduler 负责的队列被其余 Scheduler 拾起后,音讯将在生产可见期后再次出现,因而 Scheduler 2 将再次获取该音讯,做到后续的触发。
2、如果音讯曾经开始执行(步骤 3.2),当音讯在 Scheduler 2 中再次出现后,咱们依赖用户 VM 中的 Agent 进行状态治理。此时 Scheduler 2 将向对应的 Agent 发送执行申请;此时 Agent 发现该音讯曾经存在于内存中,那么将间接疏忽执行申请,并将执行的后果在执行后通过此链接告知 Scheduler 2,进而实现 Failover 的复原。
用户侧业务级别的散发去重实现
函数计算零碎可能做到对于单点故障下的每条音讯精确的生产能力,然而如果用户侧对于同一条业务数据重复触发函数执行的话,函数计算无奈辨认不同音讯是否在逻辑上是同一个工作。这种状况往往产生在网络分区。在图 2 中,如果用户调用 1 产生超时,此时有可能有两种状况:
1、音讯未达到函数计算零碎,工作未胜利提交;
2、音讯曾经达到函数计算并入队,工作提交胜利,但因为超时用户无奈得悉提交胜利的信息。
大多数状况下用户会对此次的提交进行重试。如果是第 2 种状况,那么同一个工作将被提交并执行屡次。因而函数计算须要提供一种机制,保障这种场景下业务的准确性。
函数计算提供了 TaskID 这一工作概念(StatefulAsyncInvocationID)。该 ID 全局惟一。用户每次提交工作均能够指定这样一个 ID。当产生申请超时时,用户能够进行有限次重试。所有的反复重试将在函数计算侧进行校验。函数计算外部应用 DB 对工作 Meta 数据进行存储;当有雷同 ID 进入零碎时该次申请将被回绝,并返回 400 谬误。此时客户端即可得悉工作的提交状况。
在理论应用中以 Go SDK 为例,您能够编辑如下触发工作的代码:
import fc "github.com/aliyun/fc-go-sdk"
func SubmitJob() {invokeInput := fc.NewInvokeFunctionInput("ServiceName", "FunctionName")
invokeInput = invokeInput.WithAsyncInvocation().WithStatefulAsyncInvocationID("TaskUUID")
invokeOutput, err := fcClient.InvokeFunction(invokeInput)
...
}
便提交了一个举世无双的工作。
总结
本文介绍了函数计算 Serverless Task 对于工作触发去重的相干技术细节,以便反对对于工作执行准确性有严格要求的场景。在应用 Serverless Task 后,您无需放心任何零碎组件的 Failover,您每次提交的工作将被精确执行一次。为了反对业务侧语义的散发去重,您能够在提交工作时设置工作的全局惟一 ID,应用函数计算提供的能力帮您对工作进行去重解决。
更多内容关注 Serverless 微信公众号(ID:serverlessdevs),会集 Serverless 技术最全内容,定期举办 Serverless 流动、直播,用户最佳实际。