什么是事件驱动模型?
事件驱动模型是一种计算机编程模型,它通过等待事件的触发,在事件被触发时执行对应的处理函数。这种模型下,程序不再按照严格的顺序执行命令,而是以事件为驱动进行执行。事件驱动模型更适合处理大量事件和复杂的业务场景,并且可以提高系统性能和响应速度。熟悉和运用事件驱动模型在某些场景下可以提高代码的效率和稳定性,同时增加代码的可扩展性。
为什么需要事件驱动模型?
实际的现实生活问题
假如你是一家巧克力作坊的老板,生产出美味的巧克力需要三道
工序:首先将可可豆磨成可可粉,然后将可可粉加热并加入糖变成巧克力浆,最后将巧克力浆灌入模具,撒上坚果碎,冷却后就是成品巧克力了。
最开始的时候,每次研磨出一桶可可粉后,工人就会把这桶可可粉送到加工巧克力浆的工人手上,然后再回来加工下一桶可可粉。你很快就会发现,其实工人可以不用自己运送半成品,于是他在每道工序之间都增加了一组传送带,研磨工人只要把研磨好的可可粉放到传送带上,就可以去加工下一桶可可粉了。 传送带解决了上下游工序之间的“通信”问题以及效率也提高了。
传送带上线后确实提高了生产率,但也带来了新的问题:每道工序的生产速度并不相同。在巧克力浆车间,一桶可可粉传送过来时,工人可能正在加工上一批可可粉,没有时间接收。不同工序的工人们必须协调好什么时间往传送带上放置半成品,如果出现上下游工序加工速度不一致的情况,上下游工人之间必须互相等待,确保不会出现传送带上的半成品无人接收的情况。 怎么解决呢?
答案就是加一个暂存仓库。传送带解决了半成品运输问题,仓库可以暂存一些半成品,解决了上下游生产速度不一致的问题
开发者的实际遇到的问题
拿订单创建的例子来说:
|
业务需求在不断迭代的过程中,与当前业务非强相关的主流程业务,随时都有可能被替换或者升级。
遇到促销之类的,用户下单的同时需要给每个用户赠送几个小礼品,那你又要写一个方法了,追加到后面,订单逻辑会变得越来越长。
事件驱动模型改造
|
代码解耦、之后基本只需要横向扩展。
事件驱动模型的优点
1. 并发处理:事件驱动模型可以处理多个事件同时发生的情况,而普通模型则需要按照先后顺序一个一个地处理。
2. 响应快速:事件驱动模型能够迅速响应事件的发生,而普通模型可能需要等待某些步骤完成才能进行下一步操作。
3. 易于维护和扩展:事件驱动模型中的代码更易于组合和重用,也更容易实现新的功能或扩展现有功能。
4. 资源利用高效:事件驱动模型中只有在事件发生时才会使用资源,否则资源保持空闲。而普通模型则可能会一直占用资源,浪费系统资源。具体体现? 想一下秒杀的场景
确定请求的秒杀结果后,就可以马上给用户返回响应,然后把请求的数据放入消息队列中,由消息队列异步地进行后续的操作。这样不仅响应速度更快,并且在秒杀期间,我们可以把大量的服务器资源用来处理秒杀请求。秒杀结束后再把资源用于处理后面的步骤,充分利用有限的服务器资源处理更多的秒杀请求。
事件驱动模型的例子
1、操作系统: IO模型的 阻塞、非阻塞IO、IO多路复用。(楼下买饭)。select、poll、epoll的区别?。 真正的AIO(送货上门)。
阻塞IO: 就是你早上去公司楼下买饭,发现排队的人多或者饭没有做好,你就得一直等着饭做好你才走,期间你是不能额外做其他事情的。
非阻塞IO:就是你早上去公司楼下买饭,发现排队的人多或者饭没有做好,你先到公司打个卡,每隔一段时间你就去买饭的地方看下你的早饭好了没。
IO多路复用:就是你早上去公司楼下买饭,发现排队的人多或者饭没有做好,你们公司也意识到存在这种情况,为了员工更好的健康着想(工作效率(手动狗头)),每天会派一个人在饭店等着,这个人记录了一份你们公司所有买早餐的人员名单,然后这个人会按照人员名单不断地问老板,谁的饭做好了就会通知对应的人来取。
AIO: 就是你早上去公司楼下买饭,发现排队的人多或者饭没有做好, 但是饭店提供了一根极致的服务可以送货上门,饭做好后直接送到你的工位,在此期间你就可以愉快的去公司打卡工作了,也不用再亲自去取饭了。
阻塞IO模型
非阻塞IO模型
IO多路复用-select
IO多路复用-epoll
真正的A
select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是事件驱动回调机制带来的性能提升。
2、分布式消息系统:kafka、RocketMQ。
带来的问题:
- 引入消息队列带来的延迟问题;
- 增加了系统的复杂度;
- 可能产生数据不一致的问题。
- 幂等、事务
3、Apache EventMesh等。
3、单机系统:java原生、spring内置的事件机制、Guava。
带来的问题:
- 业务逻辑难以直观看到
- 事件驱动模型通常涉及大量的事件和事件处理程序之间的交互。这可能会导致代码变得复杂和难以理解,因为不同的事件处理程序可能会产生意想不到的副作用和相互作用。
Spring框架中的事件驱动模型实际应用
Spring框架中提供了一套完善的事件机制,可以自定义事件,注册监听器,并在事件被触发时执行对应的处理函数。在Spring框架中,可以通过ApplicationContext对象的publishEvent()方法来发布自定义的事件。当事件被发布后,所有已注册的监听器都会接收到该事件并进行处理。
1. 自定义事件类
可以通过继承ApplicationEvent类来自定义一个事件类,然后在合适的位置使用ApplicationContext发布该事件。
|
2. 定义监听器
事件监听器是一个普通的Java类,负责处理特定事件的逻辑。需要实现ApplicationListener接口,并指定监听的事件类型。当监听器注册到ApplicationContext中时,它会自动接收来自ApplicationContext发布的事件,并执行对应的处理函数。
|
也可以使用@EventListener注解来注册监听器并指定其监听的事件类型。
|
3. 发布事件
|
以上例子中,首先自定义了一个MyEvent类作为事件模型,在MyEventListener类中实现了ApplicationListener接口,并重写了onApplicationEvent()方法来处理MyEvent事件。在配置文件中注册 MyEventListener 监听器,或者使用@EventListener注解,并通过ApplicationContext对象的publishEvent()方法来发布自定义事件。
事件监听器默认是同步阻塞的。如果同一个事件有多个监听器?如何指定监听器顺序?@Order(1)
4.开启异步
启动类增加此注解 @EnableAsync
监听方法上面增加注解 @Async