CloudEvents初识规范

事件无处不在。然而,事件生产者倾向于以不同的方式来描述事件。

缺乏通用的描述事件的方式意味着开发人员必须不断地重新学习如何消费事件。这也限制了类库、工具和基础设施在跨环境时发送事件数据的潜力,如SDK、事件路由器或跟踪系统等。我们从事件数据中实现的可移植性和生产力总体上受到了阻碍。CloudEvents是一个用通用格式描述事件数据的规范,以提供跨服务、跨平台和跨系统的互操作性。CloudEvents得到了大量的行业关注,从主要的云提供商到流行的SaaS公司都有。CloudEvents由云原生计算基金会(CNCF)主办,于2018年5月15日获批为云原生沙盒级项目。


一、需求背景

历史背景

CNCF无服务器工作组最初是由CNCF技术监督委员会设立的,目的是调查无服务器技术,并为CNCF在这一领域的一些相关活动提出一些可能的下一步建议。其中一项建议是调查创建一种通用的事件格式,以帮助云提供商之间的函数可移植性和事件流处理的互操作性。因此,CloudEvents 规范应运而生。虽然最初CloudEvents的工作是作为无服务器工作组的一部分完成的,但在规范达到v0.1的里程碑之后,TOC批准了CloudEvents工作,将其作为一个新的独立的CNCF沙盒项目。

需求目标

CloudEvents通常用于分布式系统中,允许服务在开发过程中松散耦合,独立部署,稍后可以连接起来创建新的应用。CloudEvents规范的目标是定义事件系统的互操作性,允许服务产生或消费事件,其中生产者和消费者可以独立开发和部署。生产者可以在消费者监听之前产生事件,而消费者可以对尚未产生的事件或事件类表达兴趣。请注意,这项工作所产生的规范侧重于事件格式的互操作性,以及事件在HTTP等各种协议上发送时如何呈现。该规范将不关注事件生产者或事件消费者的处理模型。

CloudEvents 的核心是定义了一组关于系统间传输事件的元数据(被称为属性),以及这些元数据应该如何出现在该消息中。这些元数据是将请求路由到适当的组件并促进该组件对事件进行适当处理所需的最低限度的信息集。因此,虽然这可能意味着事件本身的一些应用数据可能会和作为 CloudEvent 属性集的一部分重复,但这只是为了正确传递和处理消息的目的。不用于此目的的数据应放在事件(数据)本身中。此外,假定协议层向目标系统传递消息所需的元数据完全由协议处理,因此不包含在 CloudEvents 属性中。有关详细信息,请参阅 “非目标 “部分。在定义这些属性的同时,还将对如何以不同格式(如 JSON)和协议(如 HTTP、AMQP、Kafka)序列化事件进行规范。一些协议原生支持将多个事件批量化为一个API调用。为了帮助实现互操作性,是否需要批处理和如何实现批处理由协议决定。详情可在协议绑定或协议规范中找到。

CloudEvents 的批处理没有语义,也没有顺序。中间人可以添加或删除批处理,也可以将事件分配给不同的批次。


二、CloudEvents相关概念

事件(Event)包含和发生(Occurrence)相关的上下文和数据。每一个发生(Occurrence)都由事件的数据唯一标识。

事件代表事实,因此不包括目的地,而消息则传达意图,将数据从源头传送到特定的目的地。

Eventing

在服务器端代码中,事件通常用于连接不同的系统,其中一个系统的状态变化会导致另一个系统的代码执行。例如,当源接收到外部信号(如 HTTP 或 RPC)或观察到一个变化的值(如 IoT 传感器或非活动期)时,可能会产生一个事件。

为了说明系统如何使用 CloudEvents,下面的简化图显示了来自源的事件如何触发一个动作。

源(Source)生成消息(Message),其中事件(Event)被封装在协议中。事件到达目的地,触发一个由事件数据提供的动作(Action)。源是源类型的一个特定实例,它允许staging和测试实例。一个特定源类型的开放源软件可以由多个公司或供应商部署。

  • 事件可以通过各种行业标准协议(如HTTP、AMQP、MQTT、SMTP)、开源协议(如Kafka、NATS)或平台/供应商特定的协议(AWS Kinesis、Azure Event Grid)来递送。

  • 动作处理定义了由特定源的特定事件触发的行为或效果的事件。虽然不在本规范的范围内,但生成事件的目的通常是为了让其他系统能够轻松地对它们无法控制的源中的变化做出反应。源和动作通常是由不同的开发人员建立的。通常情况下,源是一个托管服务,而动作是无服务器函数(如AWS Lambda或Google Cloud Functions)中的自定义代码。

规范之外

以下内容被认为超出了本规范的范围。

  • 函数的建立和调用过程
  • 特定语言的运行时API
  • 选择单一身份/访问控制系统
  • 包含协议级的路由信息
  • 事件持久化过程

CloudEvents 规范将不包括协议级的路由信息(例如,发送事件的目标 URL)。这是对CloudEvents概念的新手提出的一个常见建议。经过多次讨论后,工作组得出结论:规范中没有必要包含路由信息:任何协议(如 HTTP、MQTT、XMPP 或 Pub/Sub 总线)都已经定义了路由的语义。例如,CloudEvents HTTP 绑定规定了头和请求正文内容。CloudEvents 不需要在规范中包含目标 URL 才能兼容 HTTP;HTTP 规范已经在 Request-Line 中包含了一个目标 URL。

路由信息不仅是多余的,而且会分散注意力。CloudEvents 应该增加互操作性,并将事件的生产者和消费者解耦。禁止事件格式中的路由信息,可以让CloudEvents重新传递给新的Action,或者通过包含多个通道的复杂中继器传递。例如,如果 Webhook 地址不可用,则原定用于 Webhook 的 CloudEvent 应可投递给死信队列。该死信队列应该能够将事件送入原始事件发出者从未想过的新的Action。

在系统内和跨系统产生和消费的CloudEvents会触发行为,从而产生价值。因此,归档和/或重放事件对于调试或复制来说是非常有价值的。然而,持久化事件会删除传输过程中可用的上下文信息,如生产者的身份和权利、保真验证机制或保密保护。此外,持久化会增加复杂性和挑战,以满足用户的需求。例如,重复使用私钥进行加密或签名,会增加攻击者可用的信息,从而降低安全性。预计可能会定义出有助于满足持久性要求的属性,但预计这些属性将随着行业最佳实践和进步而不断发展。


三、架构

CloudEvents 规范集定义了四种不同类型的协议元素,它们构成了一个分层架构模型。

  1. 基本规范 定义了由属性(键值对)和相关规则组成的抽象信息模型,这些属性和相关规则构成了CloudEvents。

  2. 扩展 添加了特定的、可能重叠的扩展属性和相关规则集,例如,支持不同的跟踪标准。

  3. 事件格式编码(如 JSON)定义了基本规范的信息模型和所选扩展的编码方式,以将其映射到应用协议的头和有效载荷元素。

  4. 协议绑定,例如 HTTP,定义了 CloudEvent 如何与应用协议的传输帧绑定,在 HTTP 的下是绑定 HTTP 消息。协议绑定并不约束传输帧的使用方式,这意味
    着HTTP绑定可以与任何HTTP方法以及请求和响应消息一起使用。

如果需要确保更广泛的互操作性,CloudEvents 规范集为使用特定应用协议的事件传递提供了特定的约束。HTTP Webhook 规范并不是 CloudEvents 所特有的,它可以用于将任何类型的单向事件和通知发布到符合的 HTTP 端点。然而,由于其他地方缺乏这样的规范,因此CloudEvents有必要定义它。

协议错误处理

大多数情况下,CloudEvents 规范并没有规定与创建或处理 CloudEvents 相关的处理模型。因此,如果在处理 CloudEvents 过程中出现错误,鼓励遇到错误的软件使用常规的协议级错误报告来报告错误。

属性的版本化

对于某些 CloudEvents 属性,其值所引用的实体或数据模型可能会随着时间的推移而变化。例如,dataschema 可能会引用模式文档的一个特定版本。通常情况下,这些属性值将通过在其值中包含某些特定于版本的字符串作为其值的一部分来区分每个变体。例如,可能会使用版本号(v1、v2)或日期(2018-01-01-01)。CloudEvents 规范并没有规定要使用任何特定的模式,甚至根本不要求使用版本字符串。此决定权在每个事件制生产者手中。但是,当包含特定版本字符串时,应注意其值的变化,因为事件的消费者可能会依赖现有的值,因此变化可能会被解释为 “破坏性变化”。应该在生产者和消费者之间建立某种形式的沟通,以确保事件消费者知道可能使用的值。一般来说,所有 CloudEvents 属性也是如此。

CloudEvent属性

本节提供了与CloudEvent的一些属性相关的其他背景和设计要点。

id

id 属性是指在与一个事件源相关的所有事件中都是唯一的值(每个事件源都是由其 CloudEvents 的 source 属性值唯一标识的)。虽然所使用的确切值由生产者定义,但可以保证来自单一事件源的 CloudEvents 接收者不会有两个事件共享相同的 id 值。唯一的例外情况是,如果支持事件的某些重播,在这种情况下,可以使用 id 来检测。由于一个事件的发生可能会产生一个以上的事件,所以在所有这些事件都来自同一个事件源的情况下,每个 CloudEvent 构造的事件都有一个唯一的 id。以创建 DB 条目为例,这一次发生可能会产生一个创建类型为 create 的 CloudEvent 和一个写类型为 write 的 CloudEvent。这些CloudEvents中的每一个都有一个唯一的id。如果希望这两个 CloudEvents 之间有一定的相关性,以表明它们都与同一事件相关,那么 CloudEvent 中的一些附加数据将用于此目的。在这方面,虽然事件生成者选择的确切值可能是一些随机字符串,或在其他语境中具有某种语义意义的字符串,但对于本CloudEvent属性的目的来说,这些意义并不相关,因此,将id用于唯一性检查之外的其他目的不在本规范的范围内,也不建议使用。

CloudEvent 属性扩展

为了实现既定目标,规范作者将试图限制他们在 CloudEvents 中定义的元数据属性的数量。为此,本项目所定义的属性将分为三类。

  • required (必须的)
  • optional (可选的)
  • extensions (可扩展)

正如类别名称所暗示的那样,”必需 “属性将是该组认为在所有用例中对所有事件至关重要的属性,而 “可选 “属性将在大多数情况下使用。这两种情况下的属性都将在规范本身中定义。

当小组确定一个属性不够通用,不能归入这两类,但仍然可以从定义良好的互操作性中受益,那么它们将被归入 “扩展 “类别,并被归入文档化的扩展。该规范定义了这些扩展属性在 CloudEvent 中的出现方式。

在确定提议的属性属于哪个类别时,甚至在确定是否将其包含在其中时,小组会使用用例和用户故事来解释其理由和需求。CloudEvent 规范的扩展属性是指需要包含的额外元数据,以帮助确保 CloudEvent 的正确路由和处理。与事件本身相关且在传输或处理 CloudEvent 中不需要的其他目的的附加元数据,应放在事件(数据)本身的适当扩展点中。扩展属性应保持最小化,以确保 CloudEvent 能够正确地序列化和传输。例如,事件制作者应考虑向 CloudEvent 添加扩展时可能会遇到的技术限制。例如,HTTP二进制模式使用HTTP头来传输元数据;大多数HTTP服务器会拒绝HTTP header数据过多的请求,限制低至8kb。因此,应该尽量减少扩展属性的总大小和数量。如果一个扩展变得流行,那么规范作者可能会考虑将其作为核心属性移到规范中。这意味着在正式加入规范之前,扩展机制/进程可以作为一种方式来审核新属性,然后再正式加入到规范中。

JSON扩展

正如在CloudEvents规范的JSON事件格式中的Attributes部分提到的那样,CloudEvent扩展属性被序列化为规范定义的属性的兄弟姐妹–也就是说,在JSON对象的顶层。该规范的作者花了很长时间考虑了所有选项,并决定这是最好的选择。以下是其中的一些道理。由于规范遵循 semver(语义化版本),这意味着新的属性可以被未来版本的核心规范所定义,而不需要重大的版本号改变–只要这些属性是可选的。在这种情况下,考虑一下现有的消费者会如何处理一个新的(未知)顶层属性。虽然它可以自由地忽略它,因为它是可选的,但在大多数情况下,相信这些属性仍然希望暴露在接收这些事件的应用程序中。这将允许这些应用程序支持这些属性,即使基础设施不支持这些属性。这意味着,未知的顶层属性(不管是谁定义的–不管是未来版本的规范还是事件生产者)很可能不会被忽略。因此,虽然其他一些规范定义了一个特定的属性来放置扩展属性(例如顶层扩展属性),但作者决定,在一个传入的事件中,为未知的属性设置两个不同的位置,可能会导致互操作性问题,也会给开发者带来混乱。

通常情况下,在规范正式采用之前,扩展通常是用来测试规范的新的潜在属性。如果有一个扩展类型的属性,在这个新属性中,这个新属性被序列化了,那么,如果这个属性曾经被核心规范采用,那么它将从扩展属性中晋升(从序列化的角度来看)为顶层属性。如果我们假设这个新属性将是可选的,那么随着它被核心规范采用,它将只是一个小的版本增量,所有现有的消费者仍然应该继续工作。然而,消费者将不知道这个属性将出现在哪里–在扩展属性中或作为顶层属性。这意味着他们可能需要在这两个地方都要找。如果这个属性同时出现在两个地方,但价值不同怎么办?生产者是否需要将其放在两个地方,因为他们可能会有新老消费者?虽然可能有可能定义明确的规则来解决每一个可能出现的问题,但作者决定最好是一开始就简单地避免所有问题,在序列化中只为未知的、甚至是新的属性提供一个位置。此外,还有人指出,现在的HTTP规范也遵循了类似的模式,不再建议在HTTP扩展头中用X-作为前缀。

创建CloudEvents

CloudEvents 规范特意避免过强的规定如何创建 CloudEvents。例如,它不假定原始事件源是为该事件的发生构造相关的 CloudEvents 的同一个实体。这样就可以有多种实现选择。但是,对于规范的实现者来说,了解规范作者的期望值是很有用的,因为这可能有助于确保互操作性和一致性。

如上所述,生成初始事件的实体是否是创建相应的 CloudEvent 的实体是一个实现选择。然而,当构造/填充 CloudEvents 属性的实体代表事件源行事时,这些属性的值是为了描述事件或事件源,而不是计算 CloudEvent 属性值的实体。换句话说,当事件源和 CloudEvents 生产者之间的分工对事件消费者没有实质性的意义时,规范定义的属性通常不会包含任何值来表示这种责任的分工。

这并不是说 CloudEvents 生产者不能为 CloudEvents 添加一些额外的属性,但这些属性不属于规范中互操作性定义的属性范围。这就类似于 HTTP 代理通常会尽量减少对传入消息中定义好的 HTTP 标头的更改,但它可能会添加一些包含代理特定元数据的附加标头。还值得注意的是,原始事件源和 CloudEvents 生产者之间的这种分离可以是小的或大的。这意味着,即使CloudEvents 生产者不属于原始事件源的生态系统的一部分,但如果它是代表事件源行事,并且它在事件流中的存在对事件消费者来说没有意义,那么上述指导仍然适用。当一个实体同时充当CloudEvents的接收方和发送方,以转发或转换入站事件为目的时,出站CloudEvent与入站CloudEvent的匹配程度将根据该实体的处理语义而有所不同。如果它作为代理,只是将 CloudEvents 转发到另一个事件消费者,那么出站 CloudEvent 通常与入站 CloudEvent 在规范定义的属性方面看起来与入站 CloudEvent 完全相同。但是,如果这个实体正在对CloudEvent进行某种类型的语义处理,通常会导致数据属性的值发生变化,那么它可能需要被视为一个与原始事件源不同的 “事件源”。因此,预计与事件生产者相关的CloudEvents属性(如源和id)将从传入的CloudEvent中更改。


四、协议与编码

正如规范中所表达的那样,CloudEvents工作的明确目标是 “以通用的方式描述事件数据”,并 “定义事件系统的互操作性,使服务能够产生或消费事件,生产者和消费者可以独立开发和部署”。

这种互操作性的基础是开放的数据格式和开放的协议,CloudEvents旨在提供这种开放的数据格式,并将其数据格式投射到常用的协议和常用的编码上。尽管每个软件或服务产品和项目显然可以选择自己喜欢的通信形式,但毫无疑问,对这种产品或项目私有的专有协议并不能促进事件生产者和消费者之间的广泛互操作性的目标。特别是在消息传递和事件处理领域,过去十年来,业界在开发一个强大的、广泛支持的协议基础上取得了重大进展,比如HTTP 1.1和HTTP/2以及WebSockets或事件网络上的WebSockets或事件,或者是面向连接的消息传递和遥测传输的MQTT和AMQP。一些被广泛使用的协议已经成为事实上的标准,有些协议是由三家或更多公司组成的顶级联盟组成的强大的生态系统中产生的,有些则是由一家公司发布的强大的项目生态系统中产生的,无论在哪种情况下,都是与前面提到的标准栈的演进同步进行的。

CloudEvents不应成为一种载体,甚至不应成为暗中认可或推广项目或产品专有协议的工具,因为这将会对 CloudEvents 的最初目标产生反作用。要使协议或编码符合 CloudEvents 核心事件格式或协议绑定的资格,它必须属于以下任一类别。

  • 该协议具有作为标准的正式地位,具有广泛认可的多厂商协议标准化机构(如 W3C、IETF、OASIS、ISO)的标准地位
  • 该协议在其生态系统类别中具有 “非事实标准 “的地位,这意味着它被广泛使用,以至于被认为是特定应用的标准。实际上,我们希望看到至少有一家厂商中立的
    开源组织(如Apache、Eclipse、CNCF、.NET基金会)旗下至少有一个开源实现,并且至少有十几家独立的厂商在其产品/服务中使用该协议。

除了正式地位之外,协议或编码是否符合核心 CloudEvents 事件格式或协议绑定的关键标准是,该组是否同意该规范将为与协议或编码产生的产品或项目无关的任何一方带来持续的实际利益。这方面的一个基本要求是,协议或编码的定义方式必须允许独立于产品或项目的代码之外的替代实现。


五、总结

本节介绍了一些解释CloudEvents价值的用例。

  • 跨服务和平台的事件规范化

主要的事件发布商(如AWS、微软、谷歌等)都在各自的平台上以不同的格式发布事件。甚至有少数情况下,同一提供商上的服务以不同的格式发布事件(如AWS)。这就迫使事件消费者不得不实施自定义逻辑,以实现跨平台、偶尔跨服务在单一平台上读取或合并事件数据。CloudEvents 可以为处理跨平台和跨服务的事件的消费者提供单一体验。

  • 促进跨服务和平台的集成

事件数据跨环境传输的情况越来越普遍。然而,如果没有一种通用的描述事件的方式,事件的跨环境传输就会受到阻碍。没有单一的方法来确定事件的来源和可能的去向。这就阻碍了促进事件成功交付的工具化,也阻碍了消费者知道如何处理事件数据。CloudEvents 提供了有用的元数据,中间件和消费者可以依赖这些元数据来促进事件的路由、记录、传递和接收。

  • 提高FaaS的可移植性

FaaS(也称无服务器计算)是IT领域发展最快的趋势之一,它主要是由事件驱动的。然而,FaaS的一个主要问题是供应商的锁定问题。这种锁定部分是由各供应商之间的函数API和签名的差异造成的,但这种锁定也是由函数内部接收事件数据的格式差异造成的。CloudEvents 对事件数据的通用描述方式提高了FaaS的可移植性。

  • 改进事件驱动/无服务器架构的开发和测试工作

由于缺乏通用的事件格式,使得事件驱动和无服务器架构的开发和测试变得复杂化。没有简单的方法可以准确地模拟事件用于开发和测试,并帮助在开发环境中模拟事件驱动的工作流程。CloudEvents可以使开发人员有更好的工具来构建、测试和处理事件驱动和无服务器架构的端到端生命周期。

  • 事件数据演变

大多数平台和服务对其事件的数据模型进行了不同的版本(如果他们根本不这么做的话)。这为发布和消费事件的数据模型创造了不一致的体验,因为这些数据模型在不断发展。CloudEvents 可以提供一种通用的方式来版本化和演进事件数据。这将帮助事件发布者根据最佳实践安全地对其数据模型进行版本化,这将帮助事件消费者在事件数据演进过程中安全地使用事件数据。

  • 规范化Webhooks

Webhooks是一种没有使用通用格式的事件发布方式。Webhooks 的消费者没有一致的方式来开发、测试、识别、验证和整体处理通过 Webhooks 传递的事件数据。CloudEvents 可以为 webhook 发布和消费提供一致性。

  • 政策执行

处于安全和策略的考虑,系统之间的事件的传送可能需要过滤、转换或阻止。例如,为了防止事件的进入或流出,如事件数据包含敏感信息或希望禁止发送方和接收方之间的信息流。

一个通用的事件格式将使人们更容易推理正在流转的数据,并允许对数据进行更好的反省。

  • 事件追踪

从源发送的事件可能会导致从各种中间件设备(如事件代理和网关)发送的附加事件序列。CloudEvents 包含事件中的元数据,可将这些事件作为事件序列的一部分进行关联,以进行事件追踪和故障排除。

  • IoT / 物联网

物联网设备会发送和接收与其函数相关的事件。例如,一个连接的恒温器将发送关于当前温度的遥测数据,并可以接收改变温度的事件。这些设备通常有一个受限制的操作环境(CPU、内存),需要一个定义良好的事件消息格式。在很多情况下,这些消息是二进制编码的,而不是文本的。无论是直接来自设备还是通过网关转换,CloudEvents 都可以更好地描述消息的来源和消息中包含的数据格式。

  • 事件的关联性

一个无服务器的应用程序/工作流可以与来自不同事件源/生产者的多个事件相关联。例如,一个防盗侦测应用/工作流可能同时涉及一个运动事件和一个门窗打开事件。一个无服务器平台可以接收到许多不同类型事件的实例,例如,它可以接收来自不同房屋的运动事件和门窗打开事件。无服务器平台需要将一种类型的事件实例与其他类型的事件实例正确关联,并将接收到的事件实例映射到正确的应用/工作流实例。CloudEvents 将为任何事件消费者(如无服务器平台)提供一种标准的方法,以便在事件数据中找到事件关联信息/标记,并将接收到的事件实例映射到正确的应用/工作流实例。


参考资料