作者:沈林
前言
事件驱动,这个词在部分人印象中,它是一个过时的技术——没什么新意。从时间上看,确实也是这样,上世纪 60 年代,事件驱动就已经被正式提出,经常会被在 GUI 编程中。但是在有些人印象中,事件驱动又是一个非常陌生,非常新颖的技术。
不管怎么样,现实是已经有越来越多的公司,开始或则经把事件驱动架构应用到企业的核心业务中,包括:阿里巴巴、喜力、联合利华、美国联邦航空管理局、银行资本市场等等。市场上,也有很多公司推出了自己的产品或解决方案,比如阿里云、AWS、Google,Solace。行业里也孕育出了事件的标准:CloudEventsGartener,则把事件驱动定义为未来十大趋势之一;
这个时候,我们就要问了,事件驱动架构到底是什么呢?为什么现在,被越来越多的人,开始关注事件驱动架构了呢?
5 月 28 日,GOTC 2023 全球开源技术峰会上,阿里云智能技术专家沈林发表主题演讲:Apache RocketMQ 事件驱动引擎。
Apache RocketMQ PMC&阿里云智能技术专家:沈林
什么是事件?
说到事件驱动架构,大家第一印象往往会把重点放在“架构”这两个字上,但是,事件驱动架构很大的魅力其实来源于前面“事件”两个字,所以今天,我们先一起看下什么是事件。RocketMQ 之前一直给人的印象是一个消息引擎,那为什么我们在前段时间发布的 5.0 版本中,引入了事件?消息跟事件,又有什么区别呢?
事件,如果我们查阅字典,他会给你这样一个解释:事件是指过去已经发生的事,尤其是比较重要的事。
这个很好理解啊。比如,GOTC 大会今天在上海正式开幕了;刚才我的手机铃声响了;这些都是过去已经发生的事件。
但是,如果我们接着刚才的问题问:事件跟消息有什么区别呢?这个时候,大家是不是觉得事件这个定义,好像又不那么清晰了?刚才我们说的那些事件,是不是也可以理解为消息?如果这个时候,老张给我发送了一条短信,那这个短信,算是事件,还是消息呢?
我们可以通过这张图,来简单理解消息和事件的关系。消息包含两类,一类是 Command 消息,另一类就是 Event 消息。
1、Command 消息是什么?我们看下面左边这张图,外部系统发送给本系统的一条操作命令,就是Command消息;
2、那什么是 Event 消息呢?再看下面右边这张图,本系统收到外部 Command 操作请求,系统内部发生改变之后,就产生了 Event;
所以,事件和消息稍微有些不同。事件,可以理解为是一种特殊的消息,那事件特殊在什么地方呢?主要包含 4 个方面:
事件的特性 1:已发生且不可变的
事件,一定是“已发的”。“已发生”的代表什么呢?不可变的。我们不可能改变过去,除非你有超能力。这个特性非常重要,在我们处理事件、分析事件的时候,这就意味着,我们绝对可以相信这些事件,只要是收到的事件,一定是系统真实发生过的行为,而且是 Immutable,不可修改。
对比 Command 消息,Command 的中文是什么?命令!很显然,它还是没有发生的,而是表达了一种期望。我们知道,“期望的”不一定会成功发生。比如:
- 把厨房的灯打开;
- 去按下门铃;
- 转给 A 账户 10w;
这些都是 Commond,都是期望发生的行为。但是,最终有没有发生呢?并不知道。
Event 则是明确已经发生的事情。比如:
- 厨房灯被打开了;
- 有人按了门铃;
- A 账户收到了 10w
事件的特性 2:无期望的
事件的第二个特性是:无期望的。事件是客观的描述一个事物的状态或属性值的变化,但对于如何处理事件本身并没有做任何期望。相比之下,Commond 则是有期望的,它希望系统做出改变;但是 Event,它只是客观描述系统的一个变化。我们举一个例子:交通信号灯从绿灯变成红灯,它就是一个事件。事件本身并没有任何期望,说要求行人或汽车禁止通行,而是交通法规需要红绿灯,并赋予了其规则。
所以,系统,一般不会定向的、单独向一个指定的系统发送事件,而是统一的告诉“事件中心”。“事件中心”那里面有各个系统上报上来的,各式各样的事件。系统会向事件中心说明:自己这个系统,会产生哪些事件,这些事件的格式是怎么样的;别的系统如果感兴趣,就可以来主动订阅这些事件;真正赋予事件价值的,是事件消费者。事件消费者想看看,某个系统发生了什么变化?OK,那他就去订阅这些事件,所以事件是消费者驱动的。
这跟消息有什么区别呢?Commond 消息的发送和订阅,是双方约定好的,外人不知道,往往是以文档或代码的形式,大家按约定好的协议,发送和订阅消费,这个过程往往是生产者驱动的。
打个比喻,事件就像市场经济,商品被生产出来,具体有什么价值,有多大价值,很大程度上看其消费者。我们能看到系统中各种各样的事件,就像橱窗里摆放了各种各样的商品;而 Commond 消息,有点像计划经济,一出生就带着很强的目的性,“我”就是要“分配”给谁消费。
事件的特性 3:天然有序且唯一的
事件的第三个特性是:“天然有序且唯一的”。同一个实体,不能同时发生 A 又发生 B,必有先后关系;如果是,则这两个事件必属于不同的事件类型。细心的同学,肯定发现了一点,这里其实隐藏了事件的一个额外属性:因为天然有序,跟时间轴上的某一时刻强绑定,且不能同时发生,所以它一定是唯一的。
如果我们看到了两个内容一样的事件,那么一定是发生了两次,而且一次在前,一次在后。这对于我们处理数据最终一致性、以及系统行为分析都很有价值:我们看到的,不光是系统的一个最终结果,而是看到变成这个结果之前的,一系列中间过程。
事件的特性 4:具象化
事件的第四个特性是:“具象化”。事件会尽可能的把“案发现场”完整的记录下来,因为它也不知道消费者会如何使用它,所以它会做到尽量的详尽,包括:什么时候发生?是由谁产生的事件?是什么类型的事件?是谁发送的事件?事件的唯一性标志是什么?事件的内容是什么?等等。
再对比我们常见的消息,因为上下游一般是确定的,常常为了性能和传输效率,则会做到尽可能的精简,只要满足“计划经济”指定安排的消费者需求即可。
什么是事件驱动架构?
讲完事件,我们再回过头来,一起看看,什么是事件驱动架构。为了方便理解,我们举一个简单的例子:我们都知道:交易系统在完成订单交易之后,有很多系统都“需要”知道这个订单信息,比如:
- 物流系统,需要知道这个订单信息,来安排发货;
- 积分系统,需要知道这个订单信息,来重新计算这个用户的积分;
- 营销系统,需要知道这个订单信息,来计算当天的实时交易额。
这里我们有三种方式来实现上游交易系统和下游物流、积分、营销系统的集成。
上下游集成
方式 1:主动调用
一种最简单的实现方式是:交易系统依次调用每个系统,把订单信息发出去。比如下图这种方式:
但我们都知道,这个设计,是非常糟糕。尤其当越来越多的系统加进来时,不仅开发成本高,而且万一其中一个系统出现问题,处理不好,很容易影响其他系统的发送。
方式 2:消息异步解耦
一个很自然的解决方案是:我们将订单信息,发送到中间的一个消息 broker 服务。然后,物流系统、积分系统、营销系统只需要去订阅 Broker 的这些交易订单消息就可以了,非常简单清晰。
方式 3:事件驱动架构
那如果是在事件驱动架构中,应该怎么做呢?交易系统,依旧会将交易订单发送到我们中间的 Broker 服务,但是下游服务不再需要主动订阅 Broker 中的交易订单,而往往是 Broker 推送给下游系统。这个时候,大家可能既有疑问了,这个好像跟方式 2 消息异步解耦,没有太大的区别吧?难道事件驱动架构,就是简单的把 Pull 模式变成了 Push 模式?
这里我们围绕上游和下游,看看事件驱动架构带来了什么改变。
面向下游
1、耦合的膨胀
这里我们衍生一下,很多时候,下游的营销系统它不是只依赖一个交易系统产生的订单数据,比如:它可能同时需要淘宝的交易订单、京东的交易订单、拼多多的交易订单,来实时计算一个当前时刻汇总值。这个时候,在“消息异步解偶” 的架构中,客户的营销系统需要做两件事:
第一,订阅三个 Broker 服务,来获取淘宝、京东、拼多多的交易订单数据;
第二,由于淘宝、京东、拼多多的交易订单数据格式,是不同的,所以客户的营销系统,需要对三种数据格式进行适配,先转换成营销系统内部期望的数据格式,然后再处理。
而且,如果后面客户又在抖音开店,客户的营销系统需要再适配一遍;如果某家的订单数据格式发生变化,那客户营销额系统也必须联动的进行更新。
如果在事件驱动架构中,客户的营销系统,需要怎么做呢?他什么都不需要,只要登高一声大喊:“我需要什么样的订单事件格式,我提供一个 API,其他系统就按这个订单事件格式发给我就可以了”。EventBroker 会将上游的事件转换成客户营销系统需要的数据格式,发送给他的 API。不管接多少系统的交易订单、不管外部系统怎么变,反正我不变。
2、耦合方向
这里我们分析下这三种方式的耦合关系:我们要知道,耦合是有方向性的。
- 方式 1-直接调用:是上游 A 依赖下游 B;(一旦下游 B 发生改变,A 需要同步的改变)
- 方式 2-消息异步解偶:是 B 依赖于 A;(一旦上游 A 的数据格式,发生改变,B 需要同步的改变)
- 方式 3-事件驱动:A 不依赖于 B,B 也不依赖于 A;(所有的耦合,都由中间的 Event Broker 来统一处理和维护)
3、影响系统稳定的因子有哪些?
除了降低依赖,下游系统在开发的时候,最关注的是什么?对大部分业务场景来说,最重要的是:开发维护成本低、稳定又可靠。不过,在消息异步解偶架构中,大家有没有发现,影响当前下游这个系统发生变化的入口,是不是有两个?(就是下面咖啡色的部分) 一个是 API;一个是消息订阅。
一个系统,有两个入口会引起发生变化,是一件非常麻烦的事。这意味着:我们在开发和维护这个系统的稳定性时,需要守护好两个口子:无论是身份认证、审计、安全、流控、测试、维护等等,我们都要两边考虑。不仅成本高,而且很容易出问题。
4、可测试性和可维护性
在事件驱动架构模式中,下游系统只需要提供一个 API 入口。
- 对外:这个 API,既是用来接收上游的事件,也可以用来和其他系统间的通讯;
- 对内:这个 API 的设计,是围绕下游系统当前自己的领域模型去设计的,不需要去适配任何其他系统。
所以整个系统,会非常简约。简约的好处是:当我们需要变更系统时,只需要保障我们提供的 API 可靠,可测试性和可维护性都大大提升。
5、Serverless
事件驱动还有一个非常大的优势是可以通过事件的方式,按量驱动 Serverless,去进行消费。还是在我们交易订单这个场景下:
- 有些小商家的的订单量其实没有那么多,那单独部署一个积分系统服务,7*24 小时一直跑着,是很浪费的一种行为。这个时候,如果通过事件驱动模式:当只有交易订单事件产生时,才去触发下游 Serverless 服务,按量计算付费,能够极大的降低我们的成本;
- 而对有些商家,交易订单量非常大,尤其是遇到节日大促的时候,流量峰值会非常高,这个时候,如果通过事件驱动模式,按量触发 Serverless 进行计算,能够更好的提升系统的处理能力的峰值。
- 另外,如果下游系统会因为某些异常事件,影响系统稳定性。那通过事件驱动触发 Serverless 的方式,天然的,就可以提供很好的隔离性,并实现快速恢复。
Serverless 已经逐步成为云原生时代一股势不可挡的趋势,而事件驱动和 Serverless 则是一对最好的兄弟组合。
面向上游
SaaS 集成
上面都是围绕下游展开,那对于上游系统来讲,事件驱动的意义在哪呢?我们想一下,对上游系统来讲,它最关心的是什么?它关心的肯定不是系统的稳定性和解耦这些东西,不是说这些东西不重要,而是对上游系统来讲,发送到消息 Broker,和事件 Broker 没什么区别。那什么对上游系统来说是最重要的呢?这里本质上是,上游系统希望可以和更多的系统实现集成,来打造自己的生态位。
这个怎么理解呢?我们举一个例子:门禁打卡系统。
门禁打卡系统,卖给不同的公司,需要支持员工打卡的记录信息同步到不同公司的 ERP 系统中,这个时候,如果门禁打卡系统自己 One By One 去集成适配各个公司的 ERP 系统,成本是非常高的,几乎不现实;如果不去集成,可能很多公司可能就不买你的了。
所以,对于上游系统来说,能够省心省力的与生态内的产品方便集成,是最重要的事情。而在事件驱动架构模式中,门禁打卡系统只需要以事件的形式,把员工打卡事件记录下来,交给事件中心,剩下的事情就不用操心了。事件中心会统一负责下游生态的集成对接。
另外,对于门禁打卡系统本身,它也需要知道新员工的入职事件,因为只有这样,在新员工打卡的时候,才能够及时识别。如果通过事件驱动模式,门禁打卡系统就可以轻松的在 SaaS 生态中,从零开始,快速打造自己的生态位。
如何做一个优秀的事件驱动引擎
前面讲了这么多,了解了什么是事件以及什么是事件驱动架构。也了解到事件驱动架构独特的一些魅力:为什么事件驱动架构,被越来越多的公司喜欢。
最后,我们讲一下,如果要做一个优秀的事件驱动引擎,需要具备哪些能力?我们 RocketMQ EventBridge 怎么做的?
需要什么样的能力?
第一,我们肯定得有一个事件标准。因为事件不是给自己看的,也不是给他看的,而是给所有人看的。事件没有明确的消费者,所有都是潜在的消费者,我们得规范化事件的定义,让所有人都能看得懂,一目了然;
第二,我们得有一个事件中心,事件中心里有所有系统注册上来的各种事件。这个有点类似市场经济大卖场,玲琅满目,里面分类摆放了各种各样的事件,所有人即使不买,也都可以进来瞧一瞧,看一看,有哪些事件,可能是我需要的,那就可以买回去;
第三,我们得有一个事件格式,用来描述事件的具体内容。这相当于市场经济的一个买卖契约。生产者发送的事件格式是什么,得确定下来,不能总是变;消费者以什么格式接收事件也得确定下来,不然整个市场就乱套了;
第四,我们得给消费者一个,把投递事件到目标端的能力,并且投递前,可以对事件进行过滤和转换,让它可以适配目标端 API 接收参数的格式,我们把这个过程统一叫做订阅规则。
第五,我们还得有一个存储事件的地方,就是最中间的事件总线。
如何描述事件
关于刚才提到的第一点事件标准,这个很重要。事件标准,就相当于不同系统之间交流的语言,如果语言都不通,相互交流肯定会出很多问题。我们推荐使用 CNCF 旗下的开源 CloudEvents 协议,目前已经很多公司广泛集成,算是一个事实上的标准。CloudEvent 协议也很简单,我们有一个简单的例子, 详细可以参考官网 [ 1] :
{"specversion":"1.0","type":"com.github.pull_request.opened","source":"https://github.com/cloudevents","subject":"123","id":"A234-1234-1234","time":"2018-04-05T17:31:00Z","comexampleextension1":"value","comexampleothervalue":5,"datacontenttype":"text/xml","data":"<much wow=\"xml\"/>"
}
事件中心
另外,我们必须得有一个事件中心。事件中心对于事件驱动架构来说,是非常重要的一个角色。他就像我们刚才说的市场经济的大卖场,所有的事件,在这个大卖场里,都有详细的使用说明,大家都可以进来瞧一瞧,看一看,觉得合适,就订阅买走。
至于事件中心如何管理,我们可以从API管理里学习很多经验:我们知道 API 包含注册、Schema 描述、Sample、文档、SDK、测试、监控。Event,其实也是一样,它需要在事件中心被注册,定义 Schema 描述、Sample、文档、CodeBinding、测试、监控。
这样消费者拿到这个事件的时候,才知道是什么,怎么用,用的放心。
Schema
事件的 Schema,是用来描述事件中有哪些属性、含义等等信息。为什么我们要引入Schema?一方面是,为了让下游能够理解事件的格式,方便使用事件;另一方面,也是为了限制上游发送事件的格式,发送和修改都必须保障兼容,一旦契约签订,不能轻易修改。我们推荐使用 Json Schema 和 OpenAPI 3.0。
事件过滤和转换
关于事件的过滤和转换,RocketMQ 事件驱动引擎 提供了丰富的事件过滤和转换方式。这些我就不具体一一展开了,详细大家可以上图描述。
RocketMQEventBridge 技术架构
最后,我们 RocketMQ 围绕事件驱动推出的产品,叫做 EventBridge,他的整个架构可以分为两部分:上面是我们的控制面、下面是我们的数据面。
控制面:面向上游,做好事件的管理。通过 EventSource,把上游产生的事件,管理起来,让大家找得到需要的事件,找到事件后,知道怎么用;面向下游,可以通过 EventRule,让消费者,方便的把事件转换成需要的格式,并推送给自己。中间的 EventBus,是我们存储事件的地方,底下使用的是我们 RocketMQ 自己的Broker;数据面:是事件的通道,我们除了可以通过 API 发送事件到 EventBus 之外,还可以通过 Source Connector 主动拉事件到 EventBus。消费者创建 EventRule 之后,则可以通过 Sink Connector 将事件,推送到目标端;除此之外,我们还会有:事件追踪、事件回放、事件分析、事件归档等等。
欢迎加入我们
大家如果想进一步了解 EventBridge,可以点击下方链接,也可以一起参与社区的建设。
RocketMQ EventBridge:
https://github.com/apache/rocketmq-eventbridge
RocketMQ 学习社区体验地址
RocketMQ 学习社区重磅上线!AI 互动,一秒了解 RocketMQ 功能源码。RocketMQ 学习社区是国内首个基于 AIGC 提供的知识服务社区,旨在成为 RocketMQ 学习路上的“贴身小二”。
PS:RocketMQ 社区以 RocketMQ 5.0 资料为主要训练内容,持续优化迭代中,回答内容均由人工智能模型生成,其准确性和完整性无法保证,且不代表 RocketMQ 学习社区的态度或观点。
相关链接:
[1] 官网https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md
点击此处,立即体验RocketMQ 学习社区(建议 PC 端体验完整功能)