事件驱动架构
什么是事件?我们给事件下的一个定义是: 过去已经发生的事,尤其是比较重要的事。比如说,昨天下午我做了一次核酸检测;今天上午又吃了一个冰激淋。这些都是过去已经发生的事件。但是,如果我再问:事件跟消息有什么区别?这个时候,大家是不是觉得事件这个定义,好像又不那么清晰?刚才说的那些事件,是不是也可以理解为消息啊?如果,老张给我发送了一条短信,这个算是事件,还是消息啊?平常开发过程中,“什么时候使用消息,
以下内容参考 https://mp.weixin.qq.com/s/fAw2wWvHM1JHtuG0OzDUDw
什么是事件
什么是事件?我们给事件下的一个定义是: 过去已经发生的事,尤其是比较重要的事。
比如说,昨天下午我做了一次核酸检测;今天上午又吃了一个冰激淋。这些都是过去已经发生的事件。但是,如果我再问:事件跟消息有什么区别?这个时候,大家是不是觉得事件这个定义,好像又不那么清晰?
刚才说的那些事件,是不是也可以理解为消息啊?如果,老张给我发送了一条短信,这个算是事件,还是消息啊?平常开发过程中,“什么时候使用消息,什么时候使用事件?”
不过,在回答这个问题之前,我们一起来看一个典型的微服务。
一个微服务系统和外部系统的交互,可以简单分为两部分:一是接收外部请求(就是图中上面黄色的部分);二是是调用外部服务(就是图中下面绿色的部分)。
接收外部请求,我们有两种方式:一种是提供 API,接收外部发过来的 Query 请求和 Commond 请求;另外一种是主动订阅外部 Command 消息。这两类操作,进入系统内部之后呢,我们常常还会,调用其他为微服务系统,一起协同处理,来完成一个具体的操作。当这些操作,使得系统状态发生改变时,就会产生事件。
这里呢,我们把从外部接收到的 Command 消息,和系统内部产生的事件,都称之为消息。
我们总结一下,消息和事件的关系是这样的:消息包含两部分,Command 消息和 Event 消息
- 看图中左半部分,Command 是外部系统发送给本系统的一条操作命令;
- 再看图中右半部分,Event 则是本系统收到 Command 操作请求,系统内部发生改变之后,随之而产生了事件;
所以,事件和消息是不同的,事件可以理解为是一种特殊的消息。
事件的特点
已发生、且不可变
事件,一定是“已发的”。“已发生”的代表什么呢?不可变的。我们不可能改变过去。这个特性非常重要,在我们处理事件、分析事件的时候,这就意味着,我们绝对可以相信这些事件,只要是收到的事件,一定是系统真实发生过的行为。而且是不可修改。
无期望的
事件是客观的描述一个事物的状态或属性值的变化,但对于如何处理事件本身并没有做任何期望。
相比之下,Commond 和 Query 则都是有期望的,他们希望系统做出改变或则返回结果,但是 Event 呢,它只是客观描述系统的一个变化。
天然有序
事件的第三个特性是:“天然有序”。含义:同一个实体,不能同时发生 A 又发生 B,必有先后关系;如果是,则这两个事件必属于不同的事件类型。
因为天然有序,跟时间轴上的某一时刻强绑定,且不能同时发生,所以它一定是唯一的。
如果我们看到了两个内容一样的事件,那么一定是发生了两次,而且一次在前,一次在后。(这对于我们处理数据最终一致性、以及系统行为分析都很有价值:我们看到的,不光光是系统的一个最终结果,而是看到变成这个结果之前的,一系列中间过程)
具像化
事件的第四个特性是:“具象化”的。
事件会尽可能的把“案发现场”完整的记录下来,因为它也不知道消费者会如何使用它,所以它会做到尽量的详尽,比如:
- 是由谁产生的事件?Subject
- 是什么类型的事件?Type
- 是谁发送的事件?Source
- 事件的唯一性标志是什么?Id
- 什么时候发生?Time
- 事件的内容是什么?Data
- 事件的内容有哪些信息?Dataschema
我们还是以交通信号灯举例子:
对比我们常见的消息,因为上下游一般是确定的,常常为了性能和传输效率,则会做到尽可能的精简,只要满足“计划经济”指定安排的消费者需求即可。
总结一下,事件上面的 4 个特性,是对事件巨大的一个属性加成,让事件拥有了跟普通消息不一样的“超能力”。使事件,常常被用到 4 个典型场景:事件通知、事件溯源、系统间集成和 CQRS。
事件的典型应用场景
事件通知
在 Auhting 中比如管理员删除用户池时,会产生很多事件包括用户池删除、该用户池下的 N 个应用的删除等。需要通知给其他微服务如权限服务删除相应的策略。
事件通知有多种方式:
- 发送方主动调用,适配接收方
- 接收方主动订阅,适配发送方
- 引入事件,Borker 根据接收方协议,进行灵活适配
对于第三种方式,生产者和消费者,他们都只需要关注自己系统本身就可以了。生产者,生产什么样的事件,消费者,消费什么样的数据格式,都各自以自己的业务为中心,不需要为对方做适配。真正做到我们说的高内聚低耦合,实现彻底的完全解耦。
回到我们一开始提到的典型微服务模型,对于有些场景,我们就可以变为下面这种方式:对微服务的变更操作,统一收敛到 API 操作入口,去掉 Command 消息入口。收敛入口,对于我们维护微服务,保障系统稳定性,常常非常有好处的。
事件溯源
事件溯源是什么?事件溯源简单理解就是让系统回到过去任意时刻。那怎么样,才能让系统可以回到过去呢?很简单,首先系统所有发生的变化,都得以事件的方式记录下来;然后,我们就可以通过回放事件的方式,回到过去任何一个时刻。
那为什么只有事件才能做这个事,其他普通消息不行呢?这个还是要回到我们刚才说的几个事件特性:已发生不可变的、天然有序且唯一的、而且是非常详细具体的,完整的记录了事件的案发现场。所以,对于事件溯源这个场景,事件可以说是系统的一等一的公民。举个例子:比如说,如果我们能够完整地收集路上的各种事件信息,包括信号灯、车量、天气、拥堵路况等等,那么,我们就可以“穿越时间”,回到交通现场,重新做一次决策。比如,在智慧交通场景,当我们想去验证一个调度算法的时候,我们就可以回放当时发生的所有事件,来重现现场。
大家可能觉得这个很神奇,但是,其实我们平常一直有接触,就是我们常用的代码版本-管理系统,比如:github。
系统间集成
刚才讲的第1个场景:事件通知,一般涉及到两个上下游团队的协作开发;讲的第 2 个场景:事件溯源,则一般是 1 个团队内的开发;但系统间集成,则往往面对的是三个业务团队的协作开发。这个怎么理解呢?
其实这个也很常见:比如公司里购买了 ERP 系统,同时也购买了外部考勤系统、外部营销系统服务等等。这些系统都有一个共同点,是什么?都不是我们自己开发的,是而买来的。
如果我们想把 ERP 系统的人员信息,实时且自动同步到考勤系统中去怎么办?其实这个是有点麻烦的,因为这些都不是我们自己开发的。
1、我们不能修改 ERP 系统的代码,主动去调用考勤系统,把人员变更信息发送过去;
2、也不能修改考情系统的代码,主动去调用外部 ERP 系统的 API;
但是我们可以通过事件总线,借助 webhook 或则标准 API 等等方式,收集上游的 ERP 系统产生的人员变更事件,然后进行过滤和转换,推送到下游考勤系统中去,当然,这里也可以是内部自研服务。
所以,现在的研发模式变成了:事件中心管理了所有 SaaS 服务,包括内部自研系统产生的所有事件。然后呢,我们只需要在事件中心,寻找我们需要的事件,进行订阅,对 SaaS 服务和内部自研系统,进行简单服务编排,即可完成开发。
CQRS
CQRS 中的 C 代表 Command,Command 什么意思?就是明令,一般包含:Create/Update/Delete,Q 代表 Query,是指查询。所以 CQRS 本质是读写分离:所有的写操作,在图中左边的系统中完成,然后将系统因为 Command 产生变化的事件,同步到右边的查询系统。
什么是事件驱动架构(EDA)
以下内容参考 https://mp.weixin.qq.com/s/MCdVuaV7_dwJt0Ibe4sdHg
领域事件
在业务系统中,事件指的是领域事件,而消息可以是任意数据或数据片段。
领域事件的特点包括:
- 与服务接口一样有完整的 schema,并保证 schema 向前兼容;
- 是业务流程的一部分,由业务动作触发,包含了完整(或部分但有独立语义)的业务状态变化;
- 事件消费者接收到事件后,相应修改自身的业务状态,并按需发出新的事件;消费者需要保证所有事件最终消费成功,否则会导致业务流程不完整;
- 事件需要持久化保存并长期归档,方便业务同学查询、恢复中断的业务流程、重新发起业务流程等,也方便风控及财务分析同学做离线分析。
事件驱动架构概念
事件驱动架构是一种用于设计应用的软件架构和模型。对于事件驱动系统而言,事件的捕获、通信、处理和持久保留是解决方案的核心结构。这和传统的请求驱动模型有很大不同。
许多现代应用设计都是由事件驱动的,例如必须实时利用客户数据的客户互动框架。事件驱动应用可以用任何一种编程语言来创建,因为事件驱动本身是一种编程方法,而不是一种编程语言。事件驱动架构可以最大程度减少耦合度,因此是现代化分布式应用架构的理想之选。
事件驱动架构是一种松耦合、分布式的驱动架构,收集到某应用产生的事件后实时对事件采取必要的处理后路由至下游系统,无需等待系统响应。
事件驱动架构的工作原理
事件驱动架构由事件发起者和事件使用者组成。事件的发起者会检测或感知事件,并以消息的形式来表示事件。它并不知道事件的使用者或事件引起的结果。
检测到事件后,系统会通过事件通道从事件发起者传输给事件使用者,而事件处理平台则会在该通道中以异步方式处理事件。事件发生时,需要通知事件使用者。他们可能会处理事件,也可能只是受事件的影响。
事件处理平台将对事件做出正确响应,并将活动下发给相应的事件使用者。通过这种下发活动,我们就可以看到事件的结果。
Apache Kafka 是一种分布式数据流平台,也是事件处理的常见之选。它可以实时进行事件流的发布、订阅、存储和处理。Apache Kafka 支持需要高吞吐量和可扩展性的用例,同时,通过最大程度减少某些应用中对数据共享的点对点集成需求,它可以将延迟降至毫秒级。
除此之外,还有其他一些中间件事件管理器也可用作事件处理平台。
事件驱动架构模型
事件驱动架构可以基于发布/订阅模型或事件流模型。
发布/订阅模型
这是一种基于事件流订阅的消息传递基础架构。对于该模型而言,在事件发生或公布之后,系统会将相应的消息发送给需要通知的订阅用户。
事件流模型
借助事件流模型,事件将被写入日志。事件使用者无需订阅事件流。相反,它们可以从流的任何部分读取并随时加入流。
事件流有几种不同的类型:
- 事件流处理使用诸如 Apache Kafka 等数据流平台来提取事件并处理或转换事件流。事件流处理可用于检测事件流中有用的模式。
- 简单事件处理是指事件立即在事件使用者中触发操作。
- 复杂事件处理则需要事件使用者处理一系列事件以检测模式。
事件驱动架构的优势
事件驱动架构具有以下优势:
- 高度解耦
降低事件生产者和订阅者的耦合性。事件生产者只需关注事件的发生,无需关注事件如何处理以及被分发给哪些订阅者。任何一个环节出现故障,不会影响其他业务正常运行。 - 异步执行
事件驱动架构适用于异步场景,即便是需求高峰期,收集各种来源的事件后保留在事件中心中,然后逐步分发传递事件,不会造成系统拥塞或资源过剩的情况。 - 灵活扩展
事件驱动架构中路由和过滤能力支持划分服务,便于扩展和路由分发。 - 广播能力
- 高处理性能
在事件规范统一的前提下,支持事件路由至任何系统服务,提供各种敏捷高效的部署方案。
事件驱动架构能解决什么实际问题
下面我们举几个例子来描述事件驱动架构的解耦和广播能力如何帮助解决现实工作中的问题:
解耦能力
在基于请求/响应方式的服务化架构中,上游服务按照约定的RPC接口调用下游服务,这样有一个比较严重的问题:上游服务作为数据(例如业务单据)的生产者,强依赖了作为数据消费方的下游服务所定义的接口,导致上游服务自身无法沉淀接口和数据标准。
一种更合理的方案是依赖倒置:由上游服务定义SPI,下游服务实现SPI,这样,上游服务终于有机会沉淀出自身的接口和数据标准,不再需要适配各个下游服务的接口,而是由下游服务的开发者按照接口文档来做实现。但这种设计仍然无法解决运行时上游服务仍然依赖下游服务的问题,下游服务的可用性、一致性、幂等性能力会直接影响上游服务的相关指标及实现方式,需要上下游服务开发者一起对齐方案,在出问题时一起解决。
使用事件驱动设计可以实现契约定义和运行时的全面解耦:上游服务可以沉淀自己的事件契约,在运行时无论是上游服务还是下游服务都只依赖事件Broker,下游服务的可用性和一致性等问题由事件Broker来保障。
广播能力
在供应链中台这样复杂的微服务架构中,关键的上游服务往往有多个下游服务,上游服务一般需要顺序或并发调用所有的下游服务来完成一次完整的调用。
上游服务的开发者会面临多个难题:
- 服务的可用性会被下游服务影响;
- 服务的 RT 自己无法控制;
- 下游服务之间的一致性如何保障;
- 如何实现一套可靠的重试机制;
而下游服务的开发者也有自己的问题:
- 每接入一个上游服务都需要跟服务开发者排期:谁来答疑,什么时候联调,什么时候上线;
- 上游流量如何做过滤,高峰流量是否能抗得住;
- 如何满足上游服务的可用性及RT要求;
使用事件驱动架构天然可以避免上述问题:
- 上下游完全解耦,上游服务只要保证将事件成功发送到Broker,无论有几个下游消费者,都不会影响自身的RT,也不需要考虑下游服务之间的一致性;
- 下游服务在接入新的事件时,只需要在事件管理服务中走完订阅审批流,不需要等待事件发布者排期和联调;
- 通过事件Broker提供的事件过滤能力,下游服务只需要消费与自身相关的事件流量(例如:天猫超市的计费服务只需要消费tenantId为天猫超市的采购单创建事件,而不需要消费银泰租户的采购单创建事件);
- 通过事件Broker提供的事件存储能力和重投能力,即使上游服务发送的事件流量超过了下游服务的处理能力,也只会影响下游服务的消费延迟,不会导致大量请求失败的情况。
事件驱动架构不适合什么场景
- 强依赖 Response 的场景,例如用户登录时需要检测密码强度;
- 对全局处理延迟敏感的场景,例如搜索;
- 要求服务之间保持强一致性的场景;
事件数据规范
以下内容参考 https://github.com/cloudevents/spec
为什么需要事件数据规范?
事件无处不在,然而,事件生产者倾向于以不同的方式来描述事件。若在没有统一的规范标准下会存在以下等问题:
- 一致性:缺乏通用的事件描述方式,意味着开发人员必须为每个事件源编写新的事件处理逻辑
- 无障碍环境:没有通用的事件格式意味着没有通用的库、工具和基础设施来跨环境投递事件数据
- 可移植性:整体上阻碍了我们从事件数据中实现的可移植性和生产力。
什么是 CloudEvents?
CloudEvents 是一个用通用格式描述事件数据的规范,以提供跨服务、跨平台和跨系统的互操作性。
CloudEvents 得到了大量的行业关注,从主要的云提供商到流行的 SaaS 公司都有。CloudEvents 由云原生计算基金会(CNCF)主办,于2018年5月15日获批为云原生沙盒级项目。
CloudEvents 提供了 Go、JavaScript、Java、C#、Ruby 和 Python 的 SDK,可用于构建事件路由器、跟踪系统和其他工具。
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 架构
CloudEvents 规范集定义了四种不同类型的协议元素,它们构成了一个分层架构模型。
- 基本规范定义了由属性(键值对)和相关规则组成的抽象信息模型,这些属性和相关规则构成了CloudEvents。
- 扩展添加了特定的、可能重叠的扩展属性和相关规则集,例如,支持不同的跟踪标准。
- 事件格式编码(如 JSON)定义了基本规范的信息模型和所选扩展的编码方式,以将其映射到应用协议的头和有效载荷元素。
- 协议绑定,例如 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 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。因此,应该尽量减少扩展属性的总大小和数量。如果一个扩展变得流行,那么规范作者可能会考虑将其作为核心属性移到规范中。这意味着在正式加入规范之前,扩展机制/进程可以作为一种方式来审核新属性,然后再正式加入到规范中。
创建 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 属性名称必须由 ASCII 字符集的小写字母(“a”到“z”)或数字(“0”到“9”)组成,并且必须以小写字母开头。属性名称应具有描述性和简洁性,长度不应超过 20 个字符。
术语定义
本规范定义如下术语:
- Occurrence: “Occurrence”是指在软件系统运行期间捕获描述信息。
- Event: “Event” 是表示事件及其上下文的数据记录。
- Context: Context 表示上下文,元数据将封装在 Context 属性中。应用程序代码可以使用这些信息来标识事件与系统或其他事件之间的关系。
- Data: 实际事件中有效信息载荷。
- Message: 事件通过 Message 从数据源传输到目标地址。
- Protocol: 消息可以通过各种行业标准协议(如http、amqp、mqtt、smtp)、开源协议(如kafka、nats)或平台/供应商特定协议(aws-kineis、azure-event-grid)进行传递。
上下文属性(Context Attributes)
符合本规范的每个 CloudEvent 必须包括根据需要指定的上下文属性,并且可以包括一个或多个可选的上下文属性。 参考示例:
specversion: 0.2
type: dev.knative.k8s.event
source: /apis/serving.knative.dev/v1alpha1/namespaces/default/routes/sls-cloudevent
id: 269345ff-7d0a-11e9-b1f1-00163f005e02
time: 2022-10-20T03:23:36Z
contenttype: application/json
- type: 事件类型, 通常此属性用于路由、监控、安全策略等。
- specversion: 表示CloudEvents 规范的版本。引用 0.2 版本的规范时,事件生产者必须使用 0.2 设置此值。
- source:表示事件的产生者, 也就是事件源。
- id: 事件的 id
- time: 事件的产生时间
- contenttype: 表示Data 的数据内容格式
扩展属性(Extension Attributes)
CloudEvents 生产者可以在事件中包含其他上下文属性,这些属性可能用于与事件处理相关的辅助操作。
Data
正如术语Data所定义的,CloudEvents 产生具体事件的内容信息封装在数据属性中。例如,KubernetesEventSource 所产生的 CloudEvent 的Data信息如下:
data:
{
"metadata": {
"name": "event-display.15a0a2b54007189b",
"namespace": "default",
"selfLink": "/api/v1/namespaces/default/events/event-display.15a0a2b54007189b",
"uid": "9195ff11-7b9b-11e9-b1f1-00163f005e02",
"resourceVersion": "18070551",
"creationTimestamp": "2022-10-20T07:39:30Z"
},
"involvedObject": {
"kind": "Route",
"namespace": "default",
"name": "event-display",
"uid": "31c68419-675b-11e9-a087-00163e08f3bc",
"apiVersion": "serving.knative.dev/v1alpha1",
"resourceVersion": "9242540"
},
"reason": "InternalError",
"message": "Operation cannot be fulfilled on clusteringresses.networking.internal.knative.dev \"route-31c68419-675b-11e9-a087-00163e08f3bc\": the object has been modified; please apply your changes to the latest version and try again",
"source": {
"component": "route-controller"
},
"firstTimestamp": "2022-10-20T07:39:30Z",
"lastTimestamp": "2022-10-20T07:10:51Z",
"count": 5636,
"type": "Warning",
"eventTime": null,
"reportingComponent": "",
"reportingInstance": ""
}
规范的优势
- 跨服务和平台的事件规范化
主要的事件发布商(如AWS、微软、谷歌等)都在各自的平台上以不同的格式发布事件。甚至有少数情况下,同一提供商上的服务以不同的格式发布事件(如AWS)。这就迫使事件消费者不得不实施自定义逻辑,以实现跨平台、偶尔跨服务在单一平台上读取或合并事件数据。CloudEvents 可以为处理跨平台和跨服务的事件的消费者提供单一体验。 - 促进跨服务和平台的集成
事件数据跨环境传输的情况越来越普遍。然而,如果没有一种通用的描述事件的方式,事件的跨环境传输就会受到阻碍。没有单一的方法来确定事件的来源和可能的去向。这就阻碍了促进事件成功交付的工具化,也阻碍了消费者知道如何处理事件数据。CloudEvents 提供了有用的元数据,中间件和消费者可以依赖这些元数据来促进事件的路由、记录、传递和接收。 - 提高 FaaS 的可移植性
FaaS(也称无服务器计算)是IT领域发展最快的趋势之一,它主要是由事件驱动的。然而,FaaS 的一个主要问题是供应商的锁定问题。这种锁定部分是由各供应商之间的函数 API 和签名的差异造成的,但这种锁定也是由函数内部接收事件数据的格式差异造成的。CloudEvents 对事件数据的通用描述方式提高了 FaaS 的可移植性。 - 改进事件驱动/无服务器架构的开发和测试工作
由于缺乏通用的事件格式,使得事件驱动和无服务器架构的开发和测试变得复杂化。没有简单的方法可以准确地模拟事件用于开发和测试,并帮助在开发环境中模拟事件驱动的工作流程。CloudEvents 可以使开发人员有更好的工具来构建、测试和处理事件驱动和无服务器架构的端到端生命周期。 - 事件数据演变
大多数平台和服务对其事件的数据模型进行了不同的版本。这为发布和消费事件的数据模型创造了不一致的体验,因为这些数据模型在不断发展。CloudEvents 可以提供一种通用的方式来版本化和演进事件数据。这将帮助事件发布者根据最佳实践安全地对其数据模型进行版本化,这将帮助事件消费者在事件数据演进过程中安全地使用事件数据。 - 规范化 Webhooks
Webhooks 是一种没有使用通用格式的事件发布方式。Webhooks 的消费者没有一致的方式来开发、测试、识别、验证和整体处理通过 Webhooks 传递的事件数据。CloudEvents 可以为 webhook 发布和消费提供一致性。 - 政策安全
出于安全和策略的考虑,系统之间的事件的传送可能需要过滤、转换或阻止。例如,为了防止事件的进入或流出,如事件数据包含敏感信息或希望禁止发送方和接收方之间的信息流。
一个通用的事件格式将使人们更容易推理正在流转的数据,并允许对数据进行更好的反省。 - 事件追踪
从源发送事件可能会导致从各种中间件设备(如事件代理和网关)发送的附加事件序列。CloudEvents 包含事件中的元数据,可将这些事件作为事件序列的一部分进行关联,以进行事件追踪和故障排除。 - IoT / 物联网
物联网设备会发送和接收与其函数相关的事件。例如,一个连接的恒温器将发送关于当前温度的遥测数据,并可以接收改变温度的事件。这些设备通常有一个受限制的操作环境(CPU、内存),需要一个定义良好的事件消息格式。在很多情况下,这些消息是二进制编码的,而不是文本的。无论是直接来自设备还是通过网关转换,CloudEvents 都可以更好地描述消息的来源和消息中包含的数据格式。 - 事件的关联性
一个无服务器的应用程序/工作流可以与来自不同事件源/生产者的多个事件相关联。例如,一个防盗侦测应用/工作流可能同时涉及一个运动事件和一个门窗打开事件。一个无服务器平台可以接收到许多不同类型事件的实例,例如,它可以接收来自不同房屋的运动事件和门窗打开事件。无服务器平台需要将一种类型的事件实例与其他类型的事件实例正确关联,并将接收到的事件实例映射到正确的应用/工作流实例。CloudEvents 将为任何事件消费者(如无服务器平台)提供一种标准的方法,以便在事件数据中找到事件关联信息/标记,并将接收到的事件实例映射到正确的应用/工作流实例。
如何设计事件中心
微众银行开源云原生事件驱动架构
https://github.com/apache/incubator-eventmesh/blob/master/README.zh-CN.md
阿里云开源事件中心
第一,我们得有一个事件标准。刚才,我们也讲到事件是无期望的,它没有明确的消费者,所有都是潜在的消费者,所以,我们得规范化事件的定义,让所有人都能看得懂,一目了然。
第二,我们得有一个事件中心,事件中心里面有所有系统,注册上来的各种事件,(这个跟消息不一样,我们没有消息中心,因为消息一般是定向的,是生产者和消费者约定的,有点像计划经济,消息生产出来的时候,带着很强的目的性,是给谁谁消费的。而事件有点像市场经济)这个有点类似市场经济大卖场,玲琅满目,里面分类摆放了各种各样的事件,所有人即使不买,也都可以进来瞧一瞧,看一看,有哪些事件,可能是我需要的,那就可以买回去。
第三,我们得有一个事件格式,用来描述事件的具体内容。这相当于市场经济的一个买卖契约。生产者发送的事件格式是什么,得确定下来,不能总是变;消费者以什么格式接收事件也得确定下来,不然整个市场就乱套了。
第四,我们得给消费者一个,把投递事件到目标端的能力。并且投递前,可以对事件进行过滤和转换,让它可以适配目标端 API 接收参数的格式,我们把这个过程呢,统一叫做订阅规则。
第五,我们还得有一个存储事件的地方,就是最中间的事件总线。
事件标准
选取上面介绍的 CloudEvents
它的协议也很简单,主要规范了 4 个必选字段:id,source、type、specversion;以及多个可选字段:subject、time、dataschema、datacontenttype和data。上图右边,我们有一个简单的例子,大家可以看下,这里就不具体展开了。 另外,事件的传输也需要定义一种协议,方便不同系统之间的沟通,默认支持三种 HTTP 的传输方式:Binary Content Mode、Structured Content Mode 和 Batched Content Mode。通过 HTTP 的 Content-Type,就可以区分这三种不同的模式。其中前两种,都是传递单个事件;第三种则是传递批量事件。
事件 Schema
事件的 Schema,用来描述事件中有哪些属性、对应的含义、约束等等信息。目前我们选取了 Json Schema. 和 OpenAPI 3.0,根据事件的 Schema 描述,我们可以对事件进行合法性校验。,当然 Schema 本身的修改,也需要符合兼容性原则,这里不作具体展开。
事件过滤和转换
关于事件的过滤和转换,参考阿里云开源的实现,它提供了 7 种事件过滤方式和 4 种事件转换方式,详细可以下图描述:
技术架构
我们 RocketMQ 围绕事件驱动推出的产品,叫做 EventBridge,也是我们这次要开源的新产品。 他的整个架构可以分为两部分:上面是我们的控制面、下面是我们的数据面。
控制面中最上面的 EventSource 是各个系统注册上来的事件源,这些事件可以通过 APIGateway 发送事件到事件总线,也可以通过配置的 EventSource,生成 SouceRuner,主动从我们的系统中,去拉取事件。事件到达事件总线 EventBus 之后,我们就可以配置订阅规则了 EventRule,在规则 EventRule 里我们设置事件怎么过滤,以及投递到目标端前,做哪些转换。系统基于创建的规则会生成 TargetRunner,就可以将事件推送到指定的目标端。
那这里 SouceRuner 和 TargetRunner 是什么呢?我们具体能对接哪些上下游 Source 和 Target? 这些我们都可以在下面的 SourceRegister 和 TargetRegister 提前进行注册。 所以 EventBridge 的数据面是一个开放的架构,他定义了事件处理的SPI,底下可以有多种实现。
比如,我们把 RocketMQ 的 HTTPConnector 注册到 EventBridge 中,那我们就可以把事件推送到 HTTP 服务端。 如果我们把 Kafka 的 JDBC Connector 注册到 EventBridge 中,我们就可以把事件推送到数据库。 当然,如果你的系统不是通用的像 HTTP/JDPC 等协议,也可以开发自己的 Connector,这样就能将事件实时同步到 EventBridge,或则接收来自 EventBridge 的事件。 除此之外,我们还会有一些附加的运维能力,包括:
- 完善的权限控制
- 支持事件契约定义以及运行时合法性校验
- 支持大事件发送和消费(10MB或更高)
- 事件追踪、回放, 支持长期的事件历史查询、事件索引查询、事件重投
- 事件分析
- 事件归档
- 以 OpenAPI 的形式开放了事件查询、事件重投等运维态的功能,方便被其他系统集成
分布式消息中间件选型
事件中心的核心是分布式消息中间件,目前业界主流的有 Kafka、RocketMQ、Pulsar
特性对比如下:
参考资料
https://mp.weixin.qq.com/s/fAw2wWvHM1JHtuG0OzDUDw
https://mp.weixin.qq.com/s/MCdVuaV7_dwJt0Ibe4sdHg
https://mp.weixin.qq.com/s/Ue_-0lym-G_PavSs6xe_gQ
https://zhuanlan.zhihu.com/p/158194645
https://www.npmjs.com/package/cloudevents-kafka
https://cloudevents.github.io/sdk-java/kafka.html
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)