作者:渐意
前言无论是在大数据处理领域,还是在消息处理领域,任务系统都有一个很关键的能力-任务触发去重。这个能力在一些对准确性要求极高的场景(如金融领域)中是必不可少的。作为Serverless化任务处理平台,ServerlessTask也需要提供这类保障,在用户应用层面及自身系统内部两个维度具备任务的准确触发语义。本文主要针对消息处理可靠性这一主题来介绍函数计算异步任务功能的技术细节,并展示如何在实际应用中使用函数计算所提供的这方面能力来增强任务执行的可靠性。
浅谈任务去重在讨论异步消息处理系统时,消息处理的基本语义是无法绕开的话题。在一个异步的消息处理系统(任务系统)中,一条消息的处理流程简化如下图所示:
图1
用户下发任务-进入队列-任务处理单元监听并获取消息-调度到实际worker执行
在任务消息流转过程中,任何组件(环节)可能出现的宕机等问题会导致消息的错误传递。一般的任务系统会提供至多3个层级的消息处理语义:
At-Most-Once:保证消息最多被传递一次。当出现网络分区、系统组件宕机时,可能出现消息丢失;At-Least-Once:保证消息至少被传递一次。消息传递链路支持错误重试,利用消息重发机制保证下游一定收到上游消息,但是在宕机或者网络分区的场景下,可能导致相同消息传递多次。Exactly-Once机制则可以保证消息精确被传送一次,精确一次并不是意味着在宕机或网络分区的场景下没有重传,而是重传对于接受方的状态不产生任何改变,与传送一次的结果一样。在实际生产中,往往是依赖重传机制接收方去重(幂等)来做到ExactlyOnce。函数计算能够提供任务分发的ExactlyOnce语义,即无论在何种情况下,重复的任务将被系统认为是相同的触发,进而只进行一次的任务分发。
结合图1,如果要做到任务去重,系统至少需要提供两个维度的保障:
系统侧保障:任务调度系统自身的failover不影响消息的传递正确性及唯一性;提供给用户一种机制,可以结合业务场景,做到整个业务逻辑的触发+执行去重。下面,我们将结合简化的ServerlessTask系统架构,谈一谈函数计算是如何做到上面的能力的。
函数计算异步任务触发去重的实现背景函数计算的任务系统架构如下图所示:
图2
首先,用户调用函数计算API下发一个任务(步骤1)进入系统的API-Server中,API-Server进行校验后将消息传入内部队列(步骤2.1)。后台有一个异步模块实时监听内部队列(步骤2.2),之后调用资源管理模块获取运行时资源(步骤2.2-2.3)。获取运行时资源后,调度模块将任务数据下发到VM级别的客户端中(步骤3.1),并由客户端将任务转发至实际的用户运行资源(步骤3.2)。为了做到上文中所提到的两个维度的保障,我们需要在以下层面进行支持:
系统侧保障:在步骤2.1-3.1中,任何一个中间过程的Failover只能触发一次步骤3.2的执行,即只会调度一次用户实例的运行;用户侧应用级别去重能力:能够支持用户多次反复执行步骤1,但实际只会触发一次步骤3.2的执行。系统侧优雅升级Failover时的任务分发去重保证当用户的消息进入函数计算系统中(即完成步骤2.1)后,用户的请求将收到HTTP状态码的Response,用户可以认为已经成功提交一次任务。从该任务消息进入MQ起,其生命周期便由Scheduler维护,所以Scheduler的稳定性及MQ的稳定性将直接影响系统ExactlyOnce的实现方案。
在大多数开源消息系统中(如MQ、Kafka)一般都提供消息多副本存储及唯一消费的语义。函数计算所使用的消息队列(最底层为RocketMQ)也是同样的,底层存储的3副本实现使得我们无需