• home > webfront > engineer > Architecture >

    重要的编程模型对比:事件驱动/消息驱动/数据驱动

    Author:zhoulujun Date:

    模型是对事物共性的抽象,编程模型就是对编程的共性的抽象。什么是编程的共性呢?最重要的共性就是:程序设计时,代码的抽象方式、组织方式

    模型是对事物共性的抽象,编程模型就是对编程的共性的抽象

    什么是编程的共性呢?

    最重要的共性就是:程序设计时,代码的抽象方式、组织方式或复用方式

    编程模型主要是方法与思想

    编程模型处于方法或思想性的层面,在很多情况下,也可称为编程方法、编程方式、编程模式或编程技术、编程范式。在这里就当做同一种说法。

    当面对一个新问题时,通常的想法是通过分析,不断的转化和转换,得到本质相同的熟悉的、或抽象的、简单的一个问题,这就是化归思想。把初始的问题或对象称为原型,把化归后的相对定型的模拟化或理想化的对象称为模型

    编程模型,简单地可以理解它就是模板,遇到相似问题就可以方便依模板解决,这样就简化了编程问题。不同的编程环境和不同的应用对象有不同的编程模型。


    重要的编程模型有:事件驱动、消息驱、数据驱动

    其中事件驱动是最为常见的模式,具体参看《事件驱动型架构:从EDA原理剖析其优劣

    消息驱动

    消息驱动的核心思想是“发布-订阅”,即生产者组件将数据发布到消息队列中,而消费者组件则订阅这些消息,并在需要时进行处理。

    什么是消息?

    消息是数据信息的一种存储形式。从程序的角度看,消息就是一段存储着特定数据的内存块, 数据的存储格式是设计者预先约定好的, 只要按照约定的格式读取这段内存, 就能获得消息所承载的有用信息。

    消息是有时效性的

    任何一个消息实体都是有生命周期的,它从诞生到消亡先后经历了生成、 存储、 派发、 消费共 4 个阶段:消息实体由生产者生成, 由管理者负责存储和派发, 最后由消费者消费。

    被消费者消费之后, 这个消息就算消亡了, 虽然存储消息实体的内存中可能还残留着原来的数据, 但是这些数据对于系统来讲已经没有任何意义了, 这也就是消息的时效性。

    2.png

    我们在设计程序的时候,都怀着一个梦想,即让程序对事件的响应尽可能的快,理想的情况下,程序对事件要立即响应,不能有任何延迟。这当然是不可能的,当事件发生时,程序总会因为这样那样的原因不能立即响应事件。

    为了不至于丢失事件,我们可以先在事件相关的 ISR 中把事件加工成消息并把它存储在消息缓冲区里, ISR 做完这些后立即退出。主程序忙完了别的事情之后,去查看消息缓冲区,把刚才 ISR 存储的消息读出来, 分析出事件的有关信息, 再转去执行相应的响应代码, 最终完成对本次事件的响应。

    只要整个过程的时间延迟在系统功能容许的范围之内, 这样处理就没有问题。将事件转化为消息,体现了以空间换时间的思想。再插一句,虽然事件发生后对应的 ISR 立即被触发,但是这不是严格意义上的“响应”, 顶多算是对事件的“记录”, “记录” 和“响应” 是不一样的。事件是一种客观的存在,而消息则是对这种客观存在的记录。


    事件驱动VS消息驱动

    核心概念与联系

    • 事件驱动的核心概念

      • 事件源:生成事件的实体,可以是人、系统或其他实体。

      • 事件处理器:负责监听、处理和响应事件的组件。

      • 事件:事件源生成的信息,可以是数据、状态变化或行为等。

      • 事件存储:用于存储事件的数据库或其他存储系统。

    • 消息驱动的核心概念

      • 生产者:负责将数据封装成消息并将其发送到消息队列中的组件。

      • 消费者:负责从消息队列中订阅消息并进行处理的组件。

      • 消息队列:一种异步通信的中介,用于存储和传输消息。

      • 消息:数据的封装形式,包含了数据内容以及相关的元数据。

    转化为表格:

    核心概念事件驱动消息驱动联系与区别
    触发实体事件源(人、系统或其他实体)生产者(负责封装数据并发送到消息队列的组件)事件源可以看作是特殊的生产者
    监听与处理事件处理器(负责监听、处理和响应事件的组件)消费者(负责从消息队列中订阅消息并进行处理的组件)事件处理器可以看作是特殊的消费者
    信息传递方式事件(数据、状态变化或行为等)消息(数据的封装形式,包含数据内容及元数据)消息是一种特殊的事件,事件可以视为一种消息
    存储与中介事件存储(用于存储事件的数据库或其他存储系统)消息队列(用于存储和传输消息的异步通信中介)事件存储可以被视为一种特殊的消息队列
    通信模式事件驱动(基于事件的发布-订阅模式)消息驱动(基于消息的异步通信模式)消息驱动是一种特殊的事件驱动模式
    实时性要求通常较高(需要及时响应和处理事件)可以较低(根据实际需求调整)消息驱动可以根据需求调整实时性,事件驱动通常要求较高
    应用场景实时系统、分布式系统、GUI编程等解耦生产者和消费者、异步处理、流量削峰等消息驱动适用于解耦和异步处理,事件驱动适用于实时系统领域

    从概念上来看,消息驱动和事件驱动在某种程度上是相互关联的。消息驱动可以被视为一种特殊的事件驱动模式(值得注意的点是,事件也是一种消息),其中事件源是生产者,事件处理器是消费者,而事件存储则可以被视为消息队列。在这种情况下:

    • 事件驱动的核心思想则是将系统分解为多个事件源、事件处理器和事件存储的组件,并基于事件进行异步通信和处理。

      • 事件驱动架构主要围绕事件的产生、传递、处理和响应来设计。

    • 消息驱动的核心思想是将数据封装成消息,并在不同的组件之间进行异步传输,从而实现组件之间的解耦合

      • 消息驱动架构更侧重于组件之间通过消息进行通信。消息可以包含任何形式的数据,并不仅仅关注状态的变化。消息的生产者不关心谁会消费这些消息,同样地,消费者也不需要知道消息的具体来源

    消息驱动和事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们只是一些细微区别,一般都采用相同框架,细微的区别:

    • 消息驱动:生产者A发送一个消息到消息队列,消费者B收到该消息。生产者A很明确这个消息是发给消费者B的。通常是P2P模式。

    • 事件驱动:生产者A发出一个事件,消费者B或者消费者C收到这个事件,或者没人收到这个事件,生产者A只会产生一个事件,不关心谁会处理这个事件 ,通常是发布-订阅模型。

    事件驱动机制也有一些缺点。首先,它很难实现复杂的业务逻辑,因为回调函数的执行顺序是不可控的。另外,如果回调函数内部出现错误,那么很难对错误进行调试。

    消息驱动机制也有一些缺点。首先,事件比消息封装的更高一层,它相对于事件驱动机制来说代码实现起来比较复杂,需要更多的设计和开发工作。其次,如果消息传递的顺序出错,那么系统的行为就会出现问题,因此消息驱动机制需要更多的测试和验证工作。

    在传统的编程模型中,组件 A 调用组件 B 中的方法是及时耦合的。如果组件 B 在处理方法调用时很忙或者它很慢,则组件 A 必须等待。

    • 在消息驱动系统中,组件 A 生成一条消息,表明它必须传送到组件 B 的地址。然后组件 A 发送消息并立即获得控制权,而不是等待组件 B 完成消息的处理。消息驱动系统上的组件通常有一个队列,可以在其中存储传入的消息,以防出现负载高峰。消息传递是空间解耦新标签的构建块

    • 在事件驱动系统中,组件声明它们公开事件的位置。因此,组件 A 发出事件将使用众所周知的位置来发布它们,但不知道哪些组件正在消费这些事件。这个众所周知的位置是使用有序队列实现的。有时队列是可索引的,因此消费者可以跟踪已消耗的事件和待处理的事件。

    https://developer.lightbend.com/docs/akka-guide/concepts/message-driven-event-driven.html


    数据驱动

    数据驱动核心出发点是相对于程序逻辑,人类更擅长于处理数据。

    具体参看《XX Driven:数据驱动/模型驱动/领域驱动/元数据驱动/DSL

    很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:

    • 控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。

    • 隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。

    • 机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理:

      v2-11ec670285cfea7620dbf4e2a7ddadfb_720w.webp


    使用数据驱动的前提,在于将页面内容抽象为数据表达。基于抽象后的数据,这些数据会发生怎样的变化、又是如何被改变的,这些便是数据驱动的关注点

    数据驱动和事件驱动的最大差异是开发的视角。

    • 事件驱动会关注于“操作”和“响应”,基于流程实现编码。

    • 数据驱动则会关注于“数据”和“数据的变化”,基于状态实现编码。

    数据驱动思考

    • 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;

    • 它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”

    • 数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike

    • 程序员束手无策,只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks

    • 数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。

    事件驱动VS数据驱动

    定义

    • 事件驱动:在事件驱动编程范式中,程序的执行取决于外部事件的发生,例如用户输入、系统消息或传感器信号等。程序会监听这些事件,并根据事件的发生来触发相应的处理逻辑。

    • 数据驱动:数据驱动编程范式则更专注于数据的流动和变化。程序会根据数据的状态和变化来决定执行的逻辑,而不是直接依赖外部事件。

    流程控制

    • 事件驱动:程序会注册事件处理器,当特定事件发生时,相应的事件处理器会被调用。程序的执行流程通常是异步的,因为它们依赖于外部事件的发生

    • 数据驱动:程序会持续监视数据的状态或变化,并在数据达到特定条件时执行相应的逻辑。这种方式下,程序的执行流程通常是同步的,因为它们会根据数据状态的变化自行决定何时执行

    例子

    • 事件驱动:Web开发中的前端交互通常是事件驱动的,例如点击按钮、键盘输入等。GUI应用程序也是事件驱动的,例如点击窗口关闭按钮。

    • 数据驱动:许多数据处理和分析任务是数据驱动的,例如数据库查询、机器学习模型训练等。网络数据传输中,数据的到达和处理也是数据驱动的过程。

    实时性和响应性

    • 事件驱动:适用于需要实时响应外部事件的场景,能够快速处理事件并做出相应的反应,因此具有较高的响应性。

    • 数据驱动:通常用于需要根据数据状态进行分析和决策的场景,对于处理大量数据和复杂逻辑较为适用,但可能响应性略有降低。

    并发性和同步性

    • 事件驱动:由于事件的异步性质,事件驱动程序通常较容易实现并发处理,不容易阻塞程序执行。

    • 数据驱动:数据驱动程序通常是同步执行的,可能在处理大量数据时面临并发性和同步性的挑战,需要采用适当的并发控制手段。

    适用领域

    • 事件驱动:适用于需要实时响应用户输入或外部事件的应用场景,例如图形界面、游戏开发等。

    • 数据驱动:适用于需要处理大量数据和进行复杂数据分析的应用场景,例如数据库系统、机器学习、数据挖掘等。

    如何实现

    • 事件驱动:比如发布订阅模式,如果vue的BUS总线。在Android系统中,一般的事件驱动应用都可以使用Looper和Handler来实现。

    • 数据驱动:比如观察者模式,如vue3的数据响应,通过劫持数据的方式,实现了数据驱动。当数据发生变化时,会自动触发更新视图的操作。比如Redux 和 Flux 是前端状态管理库,基于消息驱动的 Flux 架构思想,通过分发(Dispatch)行为(Action)来触发状态(State)的变化,进而触发视图的更新。在这个过程中,行为即是消息,根据不同的行为类型对应不同的状态更新逻辑。


    数据驱动更适合数据可视化平台的开发

    数据驱动更容易将视图与逻辑解绑,能快速适应变更和调整

    对于数据驱动,我们在编程实现的过程中,更多的是思考数据状态的维护和处理,而无需过于考虑 UI 的变化和事件的监听。即使我们页面 UI 全部重构了,影响到的只有模板中绑定的部分(即上面的第 3 个步骤),功能逻辑并不会受到影响。

    简单来说,基于数据模型设计的代码,即使经历了需求变更、页面结构调整、服务器接口调整,也可以快速地实现更新和支持。

    事件驱动更倾向于流程式开发,数据驱动倾向于数据状态的变更和流动

    事件驱动的特点是,以某个交互操作为起点,流程式地处理逻辑。流程式的代码,在遇到中间某个环节变更,就需要同时更新该变更点前后环节的流程交接。


    事件驱动和数据驱动一个很重要的区别在于:

    • 事件驱动是从每个事件的触发(“操作”)为中心来设计我们的代码

    • 数据驱动则是以数据为中心,通过接收事件触发和更新数据状态的方式来实现页面功能

    从事件驱动到数据驱动,可以理解为从用户交互为中心,调整成以数据的状态扭转为中心,来进行一些页面逻辑的实现

    事件驱动的方式相比于数据驱动,少了数据抽象设计的一部分,因此开发的时候可能很快就完成某个功能的实现。但从维护和拓展的角度来说,习惯数据驱动的方式,在遇到功能变更和迭代时可以更高效、更合理地进行调整。





    参看文章:

    深入理解重要的编程模型 https://zhuanlan.zhihu.com/p/419254235

    Go必知必会系列:消息驱动与事件驱动 https://juejin.cn/post/7312678013560356902

    嵌入式开发绝招:状态机+事件驱动框架 https://zhuanlan.zhihu.com/p/673095473

    事件驱动架构在 vivo 内容平台的实践 https://cloud.tencent.com/developer/article/1939024





    转载本站文章《重要的编程模型对比:事件驱动/消息驱动/数据驱动》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/engineer/Architecture/9074.html